Step 0 set work directories
set.seed(2020)
Provide directories for training images. Training images and Training fiducial points will be in different subfolders.
train_dir <- "../data/train_set/" # This will be modified for different data sets.
train_image_dir <- paste(train_dir, "images/", sep="")
train_pt_dir <- paste(train_dir, "points/", sep="")
train_label_path <- paste(train_dir, "label.csv", sep="")
Step 1: set up controls for evaluation experiments.
In this chunk, we have a set of controls for the evaluation experiments.
(T/F) whether it is on presentation day for producing the csv file
(T/F) cross-validation on the training set
(T/F) reweighting the samples for training set
(number) K, the number of CV folds
(T/F) process features for training set
(T/F) run evaluation on an independent test set
(T/F) process features for test set
(T/F) run gbm baseline model
(T/F) evaluate performance on the test set
(number) gbm.numtrees, the number of trees to use in GBM baseline
(T/F) return polynomial features matrix only
(T/F) add polynomial features to starter code features matrix
(T/F) run svm model
(T/F) train svm model
(T/F) perform model selection over a list of svm models
(T/F) evaluate performance on the test set
(T/F) run random forest model
(T/F) train random forest model
(T/F) run evaluation on the test set
(T/F) run random forest model with old features
(T/F) train random forest model with old features
(T/F) run evaluation on the test set
(T/F) run ridge model
(0/1) alpha, alpha=0 for ridge regression, alpha=1 for lasso regression
(T/F) train ridge model
(T/F) run PCA+LDA model
(T/F) run different principal components
(T/F) run LDA on training set
(T/F) run evaluation on the test set
run.presentation.day <- FALSE #presentation day flag. No training. Generate a csv file
run.cv <- TRUE # run cross-validation on the training set
sample.reweight <- FALSE # run sample reweighting in model training
K <- 5 # number of CV folds
run.feature.train <- TRUE # process features for training set
run.test <- TRUE # run evaluation on an independent test set
run.feature.test <- TRUE # process features for test set
# gbm
run.gbm.train <- FALSE # gbm(imroved) is the chosen advanced model
run.gbm.test <- TRUE # gbm(imroved) is the chosen advanced model
gbm.numtrees <- 1000 #number of trees to use in gbm
#features options
run.poly.feature <- TRUE # process poly features
run.add.poly.feature <- TRUE # and poly features to features matrix
# svm
run.svm <- TRUE # svm is the chosen advanced model
run.svm.train <- FALSE # train svm model
model.selection <- FALSE # perform model selection on svm models
run.svm.test <- TRUE # evaluate performance on the test set
# Random Forest Model with new feature
run.rf <- TRUE
train.rf <- FALSE # Train Random Forest Model
test.rf <- TRUE # Test Random Forest Model
# Random Forest Model with old feature
run.rf.old.feature <- TRUE
train.rf.old.feature <- FALSE
test.rf.old.feature <- TRUE
# ridge
run.ridge <- FALSE # ridge is the chosen advanced model
alpha <- 0 # ridge regression
train.ridge <- FALSE # train ridge model
# PCA + LDA
run.pca_lda <- TRUE # PCA + LDA is the chosen adcanced model
run.select_PC <- FALSE #run different PCs
run.lda <- FALSE # run lda on the training set
run.pca_lad.test <- TRUE # evaluate performance on the test set
Using cross-validation or independent test set evaluation, we compare the performance of models with different specifications.
Step 2: import data and train-test split
#train-test split
info <- read.csv(train_label_path)
n <- nrow(info) #get number of rows from csv
n_train <- round(n*(4/5), 0) #use 4/5 amount of data for training
train_idx <- sample(info$Index, n_train, replace = F) #grab indexes used for training
test_idx <- setdiff(info$Index, train_idx) # get indexes not used for training
Fiducial points are stored in matlab format. In this step, we read them and store them in a list.
run.presentation.day
[1] FALSE
If on presentation day, we will run our baseline and advanced model, and produce a csv file containing label predictions.
Step 3: construct features and responses
feature.R is the wrapper for all feature engineering functions and options. The function feature( ) have options that correspond to different scenarios for the project and produces an R object that contains features and responses that are required by all the models that are going to be evaluated later.
feature.R
- Input: list of images or fiducial point
- Output: an RData file that contains extracted features and corresponding responses
source("../lib/feature.R")
tm_feature_train <- NA
gbm_tm_feature_train <- NA
if(run.feature.train){
tm_feature_train <- system.time(dat_train<-feature(fiducial_pt_list,train_idx,
run.poly.feature, run.add.poly.feature))
gbm_tm_feature_train <- system.time(gbm_dat_train<-feature(fiducial_pt_list,train_idx,
FALSE, FALSE))
save(gbm_dat_train, file="../output/gbm_feature_train.RData")
save(dat_train, file="../output/feature_train.RData")
}else{
#load(file="../output/feature_train.RData")
#load(file="../output/gbm_feature_train.RData")
}
tm_feature_test <- NA
gbm_tm_feature_test <- NA
if(run.feature.test){
tm_feature_test <- system.time(dat_test <- feature(fiducial_pt_list, test_idx,
run.poly.feature, run.add.poly.feature))
gbm_tm_feature_test <- system.time(gbm_dat_test <- feature(fiducial_pt_list, test_idx,
FALSE, FALSE))
save(gbm_dat_test, file="../output/gbm_feature_test.RData")
save(dat_test, file="../output/feature_test.RData")
}else{
load(file="../output/feature_test.RData")
load(file="../output/gbm_feature_test.RData")
}
Step 4: train classification models with training features and responses; run test on test images
Call the train model and test model from library.
train.R and test.R are wrappers for all model training steps and classification/prediction steps.
train.R
- Input: a data frame containing features and labels and a parameter list.
- Output:a trained model
test.R
- Input: the fitted classification model using training data and processed features from testing images
- Input: an R object that contains a trained classifier.
- Output: training model specification
- In this Starter Code, we use logistic regression with LASSO penalty to do classification.
source("../lib/train.R")
source("../lib/test.R")
Baseline GBM Model
if (run.gbm.train){
if (sample.reweight){
gbm_dat_train$label <- as.factor(gbm_dat_train$label)
dat_train_balanced_gbm <- SMOTE(label ~ ., gbm_dat_train, perc.over = 100, perc.under=200)
table(dat_train_balanced_gbm$label)
gbm_tm_train <- system.time(gbm_train <- train_gbm(dat_train_balanced_gbm, s=0.1,
K=K, n=gbm.numtrees,w = NULL))
} else {
gbm_tm_train <- system.time(gbm_train <- train_gbm(gbm_dat_train, s=0.1,
K=K, n=gbm.numtrees,w = NULL))
}
# plot the performance
best.iter.oob <- gbm.perf(gbm_train,method="OOB") # returns out-of-bag estimated best number of trees
print(best.iter.oob)
best.iter.cv <- gbm.perf(gbm_train,method="cv") # returns K-fold cv estimate of best number of trees
print(best.iter.cv)
saveRDS(gbm_train, "../output/gbm_model.rds")
save(gbm_tm_train, best.iter.cv, file="../output/gbm_outputs.RData")
}
if(run.gbm.test){
set.seed(2020)
load(file="../output/gbm_outputs.RData")
gbm_tm_test = NA
feature_test <- as.matrix(gbm_dat_test[, 1:ncol(gbm_dat_test)-1])
gbm_train <- readRDS("../output/gbm_model.rds")
gbm_tm_test <- system.time(prob_pred_baseline<-test_gbm(gbm_train,as.data.frame(feature_test),
n=best.iter.cv,pred.type = 'response'))
label_pred_baseline <- colnames(prob_pred_baseline)[apply(prob_pred_baseline, 1, which.max)]
}
- Show GBM Accuracy and AUC
if (run.gbm.test){
load(file="../output/gbm_outputs.RData")
gbm_accu <- mean(gbm_dat_test$label == label_pred_baseline)
gbm.auc <- WeightedROC(as.numeric(label_pred_baseline), gbm_dat_test$label)
gbm_auc = WeightedAUC(gbm.auc)
cat("Time for constructing gbm training features=", gbm_tm_feature_train[1], "s \n")
cat("Time for constructing gbm testing features=", gbm_tm_feature_test[1], "s \n")
cat("The AUC of gbm model is", gbm_auc, ".\n")
cat("The accuracy of GBM baseline model is", gbm_accu*100, "%.\n")
cat("Time for training gbm model=", gbm_tm_train[1], "s \n")
cat("Time for testing model=", gbm_tm_test[1], "s \n")
}
Time for constructing gbm training features= 0.756 s
Time for constructing gbm testing features= 0.164 s
The AUC of gbm model is 0.9016105 .
The accuracy of GBM baseline model is 89.33333 %.
Time for training gbm model= 451.15 s
Time for testing model= 14.304 s
Advanced Model: Random Forest
The second advanced model is random forest. Here we use the datasets that are extracted by new feature functions. We used two models trained by both imbalanced and balanced dataset. We used SMOTE function to balance both training and testing data. For tuning the model, we set mtry = 308, tree number = 1000, and node size = 30 for the RF model using balanced data (SMOTE), and we set mtry = 308, tree number = 1500, and node size = 30 for the RF model using imbalanced data. The evaluation of the model is shown at the end of this section.
The tuning part is in a separate file named “appendix_tune_rf.rmd” in doc folder. Please feel free to check that to see the tuning process.
We also trained random forest model with the datasets that are extracted by old feature functions. That is in part 6. Thank you for reading!
if(run.rf){
## Training RF
if(train.rf){
rf_dat_train <- dat_train
rf_dat_train$label <- as.factor(rf_dat_train$label)
dat_train_balanced_SMOTE <- SMOTE(label ~ ., rf_dat_train, perc.over = 100, perc.under=200)
# Train RF by balanced data
time.rf.train.final.balanced <- system.time(
random_forest_fit_final_balanced <- random_forest_train(dat_train_balanced_SMOTE,
mtry = 308, tree_number = 1000,
node_size = 30))
save(random_forest_fit_final_balanced, file = "../output/rf_train_final_balanced.RData")
save(time.rf.train.final.balanced, file = "../output/rf_train_final_time_balanced.RData")
# Train RF by imbalanced data
time.rf.train.final.imbalanced <- system.time(
random_forest_fit_final_imbalanced <- random_forest_train(dat_train,
mtry = 308, tree_number = 1000,
node_size = 30))
save(time.rf.train.final.imbalanced, file = "../output/rf_train_final_time_imbalanced.RData")
save(random_forest_fit_final_imbalanced, file = "../output/rf_train_final_imbalanced.RData")
}else{
load("../output/rf_train_final_balanced.RData")
load("../output/rf_train_final_time_balanced.RData")
}
# Evaluation:
if(test.rf){
rf_dat_test <- dat_test
rf_dat_test$label <- as.numeric(rf_dat_test$label)
time.rf.test.final.balanced <- system.time(
rf_predicted_balanced <- as.numeric(as.vector(random_forest_test(random_forest_fit_final_balanced,
rf_dat_test))))
rf_accuracy_balanced <- mean(round(rf_predicted_balanced == rf_dat_test$label))
tpr.fpr.balanced <- WeightedROC(as.numeric(rf_predicted_balanced),rf_dat_test$label)
rf_AUC_balanced <- WeightedAUC(tpr.fpr.balanced)
cat("AUC for tuned Random Forest(balanced): ", rf_AUC_balanced,".\n")
cat("Accuracy for tuned Random Forest(balanced)", rf_accuracy_balanced*100,"%.\n")
cat("Training time for tuned Random Forest: ", time.rf.train.final.balanced[1], "s.\n")
cat("Testing time for tuned Random Forest: ", time.rf.test.final.balanced[1], "s.\n")
cat(" ","\n")
}
}
AUC for tuned Random Forest(balanced): 0.9224453 .
Accuracy for tuned Random Forest(balanced) 92.16667 %.
Training time for tuned Random Forest: 810.32 s.
Testing time for tuned Random Forest: 0.596 s.
We think RF model do not need Cross-Validation. Here is a snippet from Breiman’s official documentation: In random forests, there is no need for cross-validation or a separate test set to get an unbiased estimate of the test set error. It is estimated internally, during the run, as follows: Each tree is constructed using a different bootstrap sample from the original data. About one-third of the cases are left out of the bootstrap sample and not used in the construction of the kth tree. Put each case left out in the construction of the kth tree down the kth tree to get a classification. In this way, a test set classification is obtained for each case in about one-third of the trees. At the end of the run, take j to be the class that got most of the votes every time case n was oob. The proportion of times that j is not equal to the true class of n averaged over all cases is the oob error estimate. This has proven to be unbiased in many tests.
Alternative Model 1: SVM Model
If the given data set is imbalanced, we apply SMOTE to rebalance the data and use it to train our svm models.
sample.reweight
[1] FALSE
Tuning hyper-parameters for both linear and radial basis kernel and select the kernel method that produces the highest AUC and accuracy among the two methods.
if(run.svm){
set.seed(2020)
if(model.selection){
svm_model_auc <- rep(NA, 2)
svm_model_accu <- rep(NA, 2)
### linear kernel
if(run.cv){
best.linear.cost <- svm_linear_cost_tune(svm_training_data)
cat("The best cost for svm model with linear kernel is: ",
best.linear.cost$best.parameters$cost) # best cost is 0.01
svm.linear.train.start = proc.time()
svm_linear_mod <- svm_linear_train(svm_training_data, 0.01, K)
svm.linear.train.end = proc.time()
svm.linear.tm = svm.linear.train.end - svm.linear.train.start
save(svm_linear_mod, file="../output/svm_linear_mod.RData")
save(svm.linear.tm, file="../output/tm_svm_linear_mod.RData")
} else {
load(file="../output/svm_linear_mod.RData")
}
svm_linear_pred <- svm_test(svm_linear_mod, svm_training_data, FALSE)
# evaluate performance on linear kernel
svm_model_accu[1] <- mean(round(svm_linear_pred == svm_training_data$label))
tpr.fpr_linear <- WeightedROC(as.numeric(svm_linear_pred), svm_training_data$label)
svm_model_auc[1] <- WeightedAUC(tpr.fpr_linear)
#### radial basis kernel
if(run.cv){
best.radial.cost <- svm_radial_cost_tune(svm_training_data)
svm.rbf.train.start = proc.time()
svm_radial_mod <- svm_radial_train(svm_training_data, best.radial.cost, K)
svm.rbf.train.end = proc.time()
svm.rbf.tm = svm.rbf.train.end - svm.rbf.train.start
save(svm_radial_mod, file="../output/svm_radial_mod.RData")
save(svm.rbf.tm, file="../output/tm_svm_radial_mod.RData")
} else {
load(file="../output/svm_radial_mod.RData")
}
svm_radial_pred <- svm_test(svm_radial_mod, svm_training_data, FALSE)
# evaluate performance on rbf kernel
svm_model_accu[2] <- mean(round(svm_radial_pred == svm_training_data$label))
tpr.fpr_rbf <- WeightedROC(as.numeric(svm_radial_pred), svm_training_data$label)
svm_model_auc[2] <- WeightedAUC(tpr.fpr_rbf)
# table to display results for the two kernel methods
svm_res = matrix(rep(NA,6),ncol=3)
svm_res[,1] = svm_model_accu
svm_res[,2] = svm_model_auc
svm_res[,3] = c(svm.linear.tm[[3]], svm.rbf.tm[[3]])
colnames(svm_res) = c("Accuracy", "AUC","Running Time")
rownames(svm_res) = c("linear","radial basis")
save(svm_res, file="../output/svm_model_selection.RData")
} else {
if(run.presentation.day){
tm_svm_radial_mod < system.time(svm_radial_mod <- svm_radial_train(svm_training_data,
1, K))
} else {
load(file="../output/svm_radial_mod.RData")
load(file="../output/svm_model_selection.RData")
svm_res
}
}
}
Accuracy AUC Running Time
linear 0.9224402 0.9216738 423.341
radial basis 0.9573881 0.9557403 523.557
Since radial basis kernel has higher accuracy and AUC than linear kernel, we will choose radial basis as our kernel method for training the svm model.
- Evaluation on Testing Data
if(run.svm){
tm_svm_rbf_test <- NA
svm_testing_data <- dat_test
if(run.svm.test){
if(!run.presentation.day) {
tm_svm_rbf_test <- system.time(svm_rbf_pred <- svm_test(svm_radial_mod, svm_testing_data, FALSE))
svm_test_accu = mean(round(svm_rbf_pred == svm_testing_data$label))
tpr.fpr.rbf <- WeightedROC(as.numeric(svm_rbf_pred), svm_testing_data$label)
svm_test_auc = WeightedAUC(tpr.fpr.rbf)
save(tm_svm_rbf_test, file="../output/tm_svm_rbf_test.RData")
cat("The accuracy of svm model is", svm_test_accu*100, "%.\n")
cat("The AUC of svm model is", svm_test_auc, ".\n")
} else {
tm_svm_rbf_test <- system.time(svm_rbf_pred <- svm_test(svm_radial_mod, svm_testing_data))
svm_test_accu = mean(round(svm_rbf_pred == svm_testing_data$label))
tpr.fpr.rbf <- WeightedROC(as.numeric(svm_rbf_pred), svm_testing_data$label)
svm_test_auc = WeightedAUC(tpr.fpr.rbf)
save(tm_svm_rbf_test, file="../output/tm_svm_rbf_test.RData")
cat("The accuracy of svm model is", svm_test_accu*100, "%.\n")
cat("The AUC of svm model is", svm_test_auc, ".\n")
}
}
}
The accuracy of svm model is 90.5 %.
The AUC of svm model is 0.8768725 .
svm.rbf.tm
user system elapsed
523.557 5.437 521.159
Alternative Model 2: Ridge Model
- Apply Constructed Ridge Model to the Training Set
if(run.ridge){
tm_ridge_train <- NA
if (train.ridge){
dat_train_rebalanced <- ROSE(label ~ ., data = dat_train, seed=2020)$data
tm_ridge_train <- system.time(ridge_cv_model<-ridge_train(train_data=dat_train_rebalanced,
alpha=alpha, K=K, lambda=lambda))
save(ridge_cv_model, file="../output/ridge_cv_model.RData")
save(tm_ridge_train, file="../output/ridge_train_time.RData")
} else {
load(file="../output/ridge_cv_model.RData")
load(file="../output/ridge_train_time.RData")
}
}
- Use Cross-Validation to Choose the Optimal Lambda with Smallest MSE
if(run.ridge){
if (train.ridge){
set.seed(2020)
dat_train_rebalanced <- ROSE(label ~ ., data = dat_train, seed=2020)$data
feature_train = as.matrix(dat_train_rebalanced[,-dim(dat_train_rebalanced)[2]])
label_train = as.integer(dat_train_rebalanced$label)
ridge_model = cv.glmnet(x=feature_train, y=label_train, alpha=alpha, nfolds=K, lambda=lambda)
opt_lambda = ridge_model$lambda.min
save(opt_lambda, file="../output/ridge_optimal_lambda.RData")
} else {
load(file="../output/ridge_optimal_lambda.RData")
}
}
- Predict on Testing Set with the Optimal Lambda
if(run.ridge){
tm_ridge_test = NA
if(run.test){
load("../output/ridge_cv_model.RData")
dat_test_rebalanced <- dat_test
feature_test <- as.matrix(dat_test_rebalanced[, -dim(dat_test_rebalanced)[2]])
tm_ridge_test <- system.time(label_pred<-as.integer(ridge_test(model=ridge_cv_model,
features=feature_test,
pred.type = 'class')))
save(tm_ridge_test, file="../output/ridge_test_time.RData")
} else{
load(file="../output/ridge_test_time.RData")
}
}
if(run.ridge){
cat("Time for constructing training features=", tm_feature_train[1], "s \n")
cat("Time for constructing testing features=", tm_feature_test[1], "s \n")
cat("Time for training ridge model=", tm_ridge_train[1], "s \n")
cat("Time for testing ridge model=", tm_ridge_test[1], "s \n")
}
Time for constructing training features= 27.937 s
Time for constructing testing features= 7.355 s
Time for training ridge model= 27.49 s
Time for testing ridge model= 0.094 s
- Evaluation on Independent Testing Data
if(run.ridge){
load("../output/ridge_cv_model.RData")
dat_test_rebalanced <- dat_test
feature_test <- as.matrix(dat_test_rebalanced[, -dim(dat_test_rebalanced)[2]])
label_pred = as.integer(predict(ridge_cv_model, s=opt_lambda, newx=feature_test,
type='class'))
label_test = as.integer(dat_test_rebalanced$label)
ridge_accuracy = mean(round(label_test== label_pred))
cat("The accuracy of the ridge model is", ridge_accuracy*100, "%.\n")
ridge_AUC = auc(roc(label_pred,label_test))
cat("The AUC of the ridge model is", ridge_AUC, ".\n")
}
The accuracy of the ridge model is 80.5 %.
The AUC of the ridge model is 0.9023372 .
Alternative Model 3: PCA + LDA
load(balanced_train_data, file="../output/feature_train.RData")
Error in load(balanced_train_data, file = "../output/feature_train.RData") :
object 'balanced_train_data' not found
- Perform PCA for Dimension Reduction
Since there are over 6000 features, we implement the PCA method to reduce dimension according to the covariance matrix. We only retain PCs with large variance.
if(run.pca_lda){
balanced_test_data <- dat_test
if(run.select_PC){
#separate the features from label
dat_train_new <- balanced_train_data[,-dim(balanced_train_data)[2]]
dat_test_new <- balanced_test_data[,-dim(balanced_test_data)[2]]
#create a vector contain target number of PCs
num.pca <- c(10,50,500,1000)
train_pca <- function(num.pca){
for(i in 1:length(num.pca)){
#start time for training the model
train.model.start = proc.time()
#run PCA
pca <- prcomp(dat_train_new)
#store for each potential PC
train_pca <- data.frame(pca$x[,1:num.pca[i]],
label = balanced_train_data[dim(balanced_train_data)[2]])
pred_pca <- predict(pca,dat_test_new)
test_pca <- data.frame(pred_pca[,1:num.pca[i]],
label = balanced_test_data[dim(balanced_test_data)[2]])
#fitting the lda model
lda_pca <- lda(label ~ ., data = train_pca)
#stop time for training the model
train.model.end = proc.time()
#start time for testing the model
test.model.start = proc.time()
#predict lda model
lda_pred_pca = predict(lda_pca,test_pca[-dim(test_pca)[2]])
#end time for testing the model
test.model.end = proc.time()
#test accuracy
test_accuracy=confusionMatrix(lda_pred_pca$class, test_pca$label)$overall[1]
print(list(l1=train.model.end - train.model.start,
l2=test.model.end - test.model.start,
l3=test_accuracy))
}
}
train_pca(num.pca)
}
}
By comparing the training time, test time and accuracy, we use model with 500 PCs.
run.lda.train
[1] TRUE
- Calculate the Training and Testing Accuracy of LDA Model
if(run.pca_lda){
test.model.start = proc.time()
pred_train_lda <- predict(lda_pca_500, train_pca_500[-dim(train_pca_500)[2]])
accu_train_lda <- mean(pred_train_lda$class == train_pca_500$label)
cat("The trainig accuracy of model: LDA", "is", accu_train_lda*100, "%.\n")
#calculating the test time
if(run.test){
pred_test_lda <- predict(lda_pca_500, test_pca_500)
}
test.model.end = proc.time()
save(pred_test_lda, file="../output/fit_train.RData")
accu_test_lda <- mean(pred_test_lda$class == test_pca_500$label)
cat("The accuracy of model: LDA", "is", accu_test_lda*100, "%.\n")
tpr.fpr <- WeightedROC(as.numeric(pred_test_lda$class), test_pca_500$label)
lda_auc = WeightedAUC(tpr.fpr)
cat("The AUC of model: LDA is", lda_auc, ".\n")
}
The trainig accuracy of model: LDA is 89.94048 %.
The accuracy of model: LDA is 74 %.
The AUC of model: LDA is 0.6973767 .
Prediction performance matters, so does the running times for constructing features and for training the model, especially when the computation resource is limited.
if(run.pca_lda){
tm_train <- train.model.end - train.model.start
tm_test <- test.model.end - test.model.start
cat("Time for constructing training features =", tm_feature_train[1], "s \n")
cat("Time for constructing testing features =", tm_feature_test[1], "s \n")
cat("Time for training model =", tm_train[1], "s \n")
cat("Time for testing model =", tm_test[1], "s \n")
}
Time for constructing training features = 27.937 s
Time for constructing testing features = 7.355 s
Time for training model = 645.814 s
Time for testing model = 0.116 s
Alternative Model 4: Random Forest (RF) Model with old features:
The fourth alternative model is random forest using old features given in the starter code. Here we use the datasets that are extracted by old feature functions. We used two models trained by both imbalanced and balanced dataset. We used ROSE function to balance both training and testing data. For tuning the model, we set mtry = 308, tree number = 500, and node size = 10 for the RF model using balanced data, and we set mtry = 308, tree number = 1500, and node size = 30 for the RF model using imbalanced data. The evaluation of the model is shown at the end of this section, and we will compare this model with the RF model trained by new features.
if(run.rf.old.feature){
old_feature <- function(input_list = fiducial_pt_list, index){
old_pairwise_dist <- function(vec){
return(as.vector(dist(vec)))
}
old_pairwise_dist_result <-function(mat){
return(as.vector(apply(mat, 2, old_pairwise_dist)))
}
old_pairwise_dist_feature <- t(sapply(input_list[index], old_pairwise_dist_result))
dim(old_pairwise_dist_feature)
old_pairwise_data <- cbind(old_pairwise_dist_feature, info$label[index])
colnames(old_pairwise_data) <- c(paste("feature", 1:(ncol(old_pairwise_data)-1), sep = ""),
"label")
old_pairwise_data <- as.data.frame(old_pairwise_data)
old_pairwise_data$label <- as.factor(old_pairwise_data$label)
return(feature_df = old_pairwise_data)
}
old_tm_feature_train <- NA
if(run.feature.train){
old_tm_feature_train <- system.time(old_dat_train <- old_feature(fiducial_pt_list, train_idx))
save(old_dat_train, file="../output/feature_train_old.RData")
}else{
load(file="../output/feature_train_old.RData")
}
old_tm_feature_test <- NA
if(run.feature.test){
old_tm_feature_test <- system.time(old_dat_test <- old_feature(fiducial_pt_list, test_idx))
save(old_dat_test, file="../output/feature_test_old.RData")
}else{
load(file="../output/feature_test_old.RData")
}
# Train Model
if(train.rf.old.feature){
# transfer label column from factor to numeric
old_dat_train$label <- as.numeric(old_dat_train$label)
old_dat_test$label <- as.numeric(old_dat_test$label)
# Balance data
dat_train$label <- as.factor(dat_train$label)
old_dat_train_balanced_SMOTE <- SMOTE(label ~ ., dat_train, perc.over = 100, perc.under=200)
# Balanced
old_time.rf.train.final.balanced <- system.time(
old_random_forest_fit_final_balanced <- old_random_forest_train(old_dat_train_balanced_SMOTE,
mtry = 308, tree_number = 1000,
node_size = 30))
save(old_random_forest_fit_final_balanced,
file = "../output/rf_train_final_balanced_old_feature.RData")
save(old_time.rf.train.final.balanced,
file = "../output/rf_train_final_time_balanced_old_feature.RData")
}else{
load("../output/rf_train_final_balanced_old_feature.RData")
load("../output/rf_train_final_time_balanced_old_feature.RData")
}
# Evaluate Model
old_rf_dat_test <- old_dat_test
old_rf_dat_test$label <- as.numeric(old_rf_dat_test$label)
if(test.rf.old.feature){
old_time.rf.test.final.balanced <- system.time(
rf_predicted_balanced <- as.numeric(as.vector(old_random_forest_test(old_random_forest_fit_final_balanced, old_rf_dat_test))))
old_rf_accuracy_balanced <- mean(round(rf_predicted_balanced == old_rf_dat_test$label))
old_tpr.fpr.balanced <- WeightedROC(as.numeric(rf_predicted_balanced),old_rf_dat_test$label)
old_rf_AUC_balanced <- WeightedAUC(old_tpr.fpr.balanced)
cat("AUC(balanced) for Random Forest with old feature: ", old_rf_AUC_balanced,".\n")
cat("Accuracy(balanced) for Random Forest with old feature", old_rf_accuracy_balanced*100,"%.\n")
cat("Training time (balanced) for Random Forest with old feature: ", old_time.rf.train.final.balanced[1], "s.\n")
cat("Testing time (balanced) for Random Forest with old feature: ", old_time.rf.test.final.balanced[1], "s.\n")
cat(" ","\n")
}
}
AUC(balanced) for Random Forest with old feature: 0.9145861 .
Accuracy(balanced) for Random Forest with old feature 88.33333 %.
Training time (balanced) for Random Forest with old feature: 462.69 s.
Testing time (balanced) for Random Forest with old feature: 0.29 s.
Generate a csv on presentation day
run.presentation.day
[1] FALSE
Reference
- Du, S., Tao, Y., & Martinez, A. M. (2014). Compound facial expressions of emotion. Proceedings of the National Academy of Sciences, 111(15), E1454-E1462.
LS0tCnRpdGxlOiAiUHJvamVjdCAzOiBGYWNpYWwgRXhwcmVzc2lvbiBQcmVkaWN0aXZlIE1vZGVsaW5nIgpzdWJ0aXRsZTogIkdyb3VwIDUiCmF1dGhvcjogIkppbmdiaW4gQ2FvLCBDaHVhbmNodWFuIExpdSwgRGVubmlzIFNocGl0cywgWWluZ3lhbyBXdSwgWmlrdW4gWmh1YW5nIgpvdXRwdXQ6CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKLS0tCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojVGVzdCBCcmFuY2ggY3JlYXRlZAppZighcmVxdWlyZSgiUi5tYXRsYWIiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiUi5tYXRsYWIiKQp9CmlmKCFyZXF1aXJlKCJyZWFkeGwiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygicmVhZHhsIikKfQppZighcmVxdWlyZSgiZHBseXIiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiZHBseXIiKQp9CmlmKCFyZXF1aXJlKCJyZWFkeGwiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygicmVhZHhsIikKfQppZighcmVxdWlyZSgiZ2dwbG90MiIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikKfQppZighcmVxdWlyZSgiY2FyZXQiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiY2FyZXQiKQp9CmlmKCFyZXF1aXJlKCJnbG1uZXQiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiZ2xtbmV0IikKfQppZighcmVxdWlyZSgiV2VpZ2h0ZWRST0MiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiV2VpZ2h0ZWRST0MiKQp9CmlmKCFyZXF1aXJlKCJnYm0iKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiZ2JtIikKfQppZighcmVxdWlyZSgiRE13UiIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJETXdSIikKfQppZighcmVxdWlyZSgiT3BlbkltYWdlUiIpKXsKIGluc3RhbGwucGFja2FnZXMoIk9wZW5JbWFnZVIiKQp9CmlmKCFyZXF1aXJlKCJBVUMiKSl7CiBpbnN0YWxsLnBhY2thZ2VzKCJBVUMiKQp9CmlmKCFyZXF1aXJlKCJlMTA3MSIpKXsKIGluc3RhbGwucGFja2FnZXMoImUxMDcxIikKfQppZighcmVxdWlyZSgicmFuZG9tRm9yZXN0IikpewogaW5zdGFsbC5wYWNrYWdlcygicmFuZG9tRm9yZXN0IikKfQppZighcmVxdWlyZSgieGdib29zdCIpKXsKIGluc3RhbGwucGFja2FnZXMoInhnYm9vc3QiKQp9CmlmKCFyZXF1aXJlKCJ0aWJibGUiKSl7CiBpbnN0YWxsLnBhY2thZ2VzKCJ0aWJibGUiKQp9CmlmKCFyZXF1aXJlKCJST1NFIikpewogaW5zdGFsbC5wYWNrYWdlcygiUk9TRSIpCn0KaWYoIXJlcXVpcmUoInRpZHl2ZXJzZSIpKXsKIGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpCn0KaWYoIXJlcXVpcmUoImNhVG9vbHMiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiY2FUb29scyIpCn0KaWYoIXJlcXVpcmUoInByZWRpY3Rpb24iKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygicHJlZGljdGlvbiIpCn0KaWYoIXJlcXVpcmUoInBST0MiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygicFJPQyIpCn0KaWYoIXJlcXVpcmUoIkRNd1IiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiRE13UiIpCn0KaWYoIXJlcXVpcmUoIk1BU1MiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiTUFTUyIpCn0KbGlicmFyeShSLm1hdGxhYikKbGlicmFyeShyZWFkeGwpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShjYXJldCkKbGlicmFyeShnbG1uZXQpCmxpYnJhcnkoV2VpZ2h0ZWRST0MpCmxpYnJhcnkoZ2JtKQpsaWJyYXJ5KERNd1IpCiMjIyBuZXcgbGlicmFyaWVzCmxpYnJhcnkoT3BlbkltYWdlUikKbGlicmFyeShBVUMpCmxpYnJhcnkoZTEwNzEpCmxpYnJhcnkocmFuZG9tRm9yZXN0KQpsaWJyYXJ5KHhnYm9vc3QpCmxpYnJhcnkodGliYmxlKQpsaWJyYXJ5KFJPU0UpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGNhVG9vbHMpCmxpYnJhcnkocHJlZGljdGlvbikKbGlicmFyeShwUk9DKQpsaWJyYXJ5KE1BU1MpCmBgYAoKCiMjIyBTdGVwIDAgc2V0IHdvcmsgZGlyZWN0b3JpZXMKYGBge3Igd2tkaXIsIGV2YWw9RkFMU0V9CnNldC5zZWVkKDIwMjApCmBgYAoKUHJvdmlkZSBkaXJlY3RvcmllcyBmb3IgdHJhaW5pbmcgaW1hZ2VzLiBUcmFpbmluZyBpbWFnZXMgYW5kIFRyYWluaW5nIGZpZHVjaWFsIHBvaW50cyB3aWxsIGJlIGluIGRpZmZlcmVudCBzdWJmb2xkZXJzLgoKYGBge3J9CnRyYWluX2RpciA8LSAiLi4vZGF0YS90cmFpbl9zZXQvIiAjIFRoaXMgd2lsbCBiZSBtb2RpZmllZCBmb3IgZGlmZmVyZW50IGRhdGEgc2V0cy4KdHJhaW5faW1hZ2VfZGlyIDwtIHBhc3RlKHRyYWluX2RpciwgImltYWdlcy8iLCBzZXA9IiIpCnRyYWluX3B0X2RpciA8LSBwYXN0ZSh0cmFpbl9kaXIsICAicG9pbnRzLyIsIHNlcD0iIikKdHJhaW5fbGFiZWxfcGF0aCA8LSBwYXN0ZSh0cmFpbl9kaXIsICJsYWJlbC5jc3YiLCBzZXA9IiIpCmBgYAoKIyMjIFN0ZXAgMTogc2V0IHVwIGNvbnRyb2xzIGZvciBldmFsdWF0aW9uIGV4cGVyaW1lbnRzLgoKSW4gdGhpcyBjaHVuaywgd2UgaGF2ZSBhIHNldCBvZiBjb250cm9scyBmb3IgdGhlIGV2YWx1YXRpb24gZXhwZXJpbWVudHMuCgorIChUL0YpIHdoZXRoZXIgaXQgaXMgb24gcHJlc2VudGF0aW9uIGRheSBmb3IgcHJvZHVjaW5nIHRoZSBjc3YgZmlsZQorIChUL0YpIGNyb3NzLXZhbGlkYXRpb24gb24gdGhlIHRyYWluaW5nIHNldAorIChUL0YpIHJld2VpZ2h0aW5nIHRoZSBzYW1wbGVzIGZvciB0cmFpbmluZyBzZXQKKyAobnVtYmVyKSBLLCB0aGUgbnVtYmVyIG9mIENWIGZvbGRzCisgKFQvRikgcHJvY2VzcyBmZWF0dXJlcyBmb3IgdHJhaW5pbmcgc2V0CisgKFQvRikgcnVuIGV2YWx1YXRpb24gb24gYW4gaW5kZXBlbmRlbnQgdGVzdCBzZXQKKyAoVC9GKSBwcm9jZXNzIGZlYXR1cmVzIGZvciB0ZXN0IHNldAoKKyAoVC9GKSBydW4gZ2JtIGJhc2VsaW5lIG1vZGVsCisgKFQvRikgZXZhbHVhdGUgcGVyZm9ybWFuY2Ugb24gdGhlIHRlc3Qgc2V0CisgKG51bWJlcikgZ2JtLm51bXRyZWVzLCB0aGUgbnVtYmVyIG9mIHRyZWVzIHRvIHVzZSBpbiBHQk0gYmFzZWxpbmUKCisgKFQvRikgcmV0dXJuIHBvbHlub21pYWwgZmVhdHVyZXMgbWF0cml4IG9ubHkKKyAoVC9GKSBhZGQgcG9seW5vbWlhbCBmZWF0dXJlcyB0byBzdGFydGVyIGNvZGUgZmVhdHVyZXMgbWF0cml4CgorIChUL0YpIHJ1biBzdm0gbW9kZWwKKyAoVC9GKSB0cmFpbiBzdm0gbW9kZWwKKyAoVC9GKSBwZXJmb3JtIG1vZGVsIHNlbGVjdGlvbiBvdmVyIGEgbGlzdCBvZiBzdm0gbW9kZWxzCisgKFQvRikgZXZhbHVhdGUgcGVyZm9ybWFuY2Ugb24gdGhlIHRlc3Qgc2V0CgorIChUL0YpIHJ1biByYW5kb20gZm9yZXN0IG1vZGVsCisgKFQvRikgdHJhaW4gcmFuZG9tIGZvcmVzdCBtb2RlbAorIChUL0YpIHJ1biBldmFsdWF0aW9uIG9uIHRoZSB0ZXN0IHNldAoKKyAoVC9GKSBydW4gcmFuZG9tIGZvcmVzdCBtb2RlbCB3aXRoIG9sZCBmZWF0dXJlcworIChUL0YpIHRyYWluIHJhbmRvbSBmb3Jlc3QgbW9kZWwgd2l0aCBvbGQgZmVhdHVyZXMKKyAoVC9GKSBydW4gZXZhbHVhdGlvbiBvbiB0aGUgdGVzdCBzZXQKCisgKFQvRikgcnVuIHJpZGdlIG1vZGVsCisgKDAvMSkgYWxwaGEsIGFscGhhPTAgZm9yIHJpZGdlIHJlZ3Jlc3Npb24sIGFscGhhPTEgZm9yIGxhc3NvIHJlZ3Jlc3Npb24KKyAoVC9GKSB0cmFpbiByaWRnZSBtb2RlbAoKKyAoVC9GKSBydW4gUENBK0xEQSBtb2RlbAorIChUL0YpIHJ1biBkaWZmZXJlbnQgcHJpbmNpcGFsIGNvbXBvbmVudHMKKyAoVC9GKSBydW4gTERBIG9uIHRyYWluaW5nIHNldAorIChUL0YpIHJ1biBldmFsdWF0aW9uIG9uIHRoZSB0ZXN0IHNldAoKYGBge3IgZXhwX3NldHVwfQpydW4ucHJlc2VudGF0aW9uLmRheSA8LSBGQUxTRSAjcHJlc2VudGF0aW9uIGRheSBmbGFnLiBObyB0cmFpbmluZy4gR2VuZXJhdGUgYSBjc3YgZmlsZQpydW4uY3YgPC0gVFJVRSAjIHJ1biBjcm9zcy12YWxpZGF0aW9uIG9uIHRoZSB0cmFpbmluZyBzZXQKc2FtcGxlLnJld2VpZ2h0IDwtIEZBTFNFICMgcnVuIHNhbXBsZSByZXdlaWdodGluZyBpbiBtb2RlbCB0cmFpbmluZwpLIDwtIDUgICMgbnVtYmVyIG9mIENWIGZvbGRzCnJ1bi5mZWF0dXJlLnRyYWluIDwtIEZBTFNFICMgcHJvY2VzcyBmZWF0dXJlcyBmb3IgdHJhaW5pbmcgc2V0CnJ1bi50ZXN0IDwtIFRSVUUgIyBydW4gZXZhbHVhdGlvbiBvbiBhbiBpbmRlcGVuZGVudCB0ZXN0IHNldApydW4uZmVhdHVyZS50ZXN0IDwtIFRSVUUgIyBwcm9jZXNzIGZlYXR1cmVzIGZvciB0ZXN0IHNldAoKIyBnYm0KcnVuLmdibS50cmFpbiA8LSBGQUxTRSAjIGdibShpbXJvdmVkKSBpcyB0aGUgY2hvc2VuIGFkdmFuY2VkIG1vZGVsCnJ1bi5nYm0udGVzdCA8LSBUUlVFICMgZ2JtKGltcm92ZWQpIGlzIHRoZSBjaG9zZW4gYWR2YW5jZWQgbW9kZWwKZ2JtLm51bXRyZWVzIDwtIDEwMDAgI251bWJlciBvZiB0cmVlcyB0byB1c2UgaW4gZ2JtCgojZmVhdHVyZXMgb3B0aW9ucwpydW4ucG9seS5mZWF0dXJlIDwtIFRSVUUgIyBwcm9jZXNzIHBvbHkgZmVhdHVyZXMKcnVuLmFkZC5wb2x5LmZlYXR1cmUgPC0gVFJVRSAjIGFuZCBwb2x5IGZlYXR1cmVzIHRvIGZlYXR1cmVzIG1hdHJpeAoKIyBzdm0KcnVuLnN2bSA8LSBUUlVFICMgc3ZtIGlzIHRoZSBjaG9zZW4gYWR2YW5jZWQgbW9kZWwKcnVuLnN2bS50cmFpbiA8LSBGQUxTRSAjIHRyYWluIHN2bSBtb2RlbAptb2RlbC5zZWxlY3Rpb24gPC0gRkFMU0UgIyBwZXJmb3JtIG1vZGVsIHNlbGVjdGlvbiBvbiBzdm0gbW9kZWxzCnJ1bi5zdm0udGVzdCA8LSBUUlVFICMgZXZhbHVhdGUgcGVyZm9ybWFuY2Ugb24gdGhlIHRlc3Qgc2V0CgojIFJhbmRvbSBGb3Jlc3QgTW9kZWwgd2l0aCBuZXcgZmVhdHVyZQpydW4ucmYgPC0gVFJVRQp0cmFpbi5yZiA8LSBGQUxTRSAjIFRyYWluIFJhbmRvbSBGb3Jlc3QgTW9kZWwKdGVzdC5yZiA8LSBUUlVFICMgVGVzdCBSYW5kb20gRm9yZXN0IE1vZGVsCgojIFJhbmRvbSBGb3Jlc3QgTW9kZWwgd2l0aCBvbGQgZmVhdHVyZQpydW4ucmYub2xkLmZlYXR1cmUgPC0gVFJVRSAjIHJ1biByYW5kb20gZm9yZXN0IHVzaW5nIG9sZCBmZWF0dXJlcwp0cmFpbi5yZi5vbGQuZmVhdHVyZSA8LSBGQUxTRSAjIHRyYWluIHJhbmRvbSBmb3Jlc3Qgd2l0aCBvbGQgZmVhdHVyZXMKdGVzdC5yZi5vbGQuZmVhdHVyZSA8LSBUUlVFICMgZXZhbHVhdGUgcGVyZm9ybWFuY2Ugb24gdGVzdCBzZXQKCiMgcmlkZ2UKcnVuLnJpZGdlIDwtIFRSVUUgIyByaWRnZSBpcyB0aGUgY2hvc2VuIGFkdmFuY2VkIG1vZGVsCmFscGhhIDwtIDAgIyByaWRnZSByZWdyZXNzaW9uIGFscGhhCmxhbWJkYSA8LSAxMF5zZXEoMTAsIC0yLCBsZW5ndGggPSAxMDApICMgbGFtYmRhCnRyYWluLnJpZGdlIDwtIEZBTFNFICMgdHJhaW4gcmlkZ2UgbW9kZWwKCiMgUENBICsgTERBCnJ1bi5wY2FfbGRhIDwtIFRSVUUgIyBQQ0EgKyBMREEgaXMgdGhlIGNob3NlbiBhZGNhbmNlZCBtb2RlbApydW4uc2VsZWN0X1BDIDwtIEZBTFNFICNydW4gZGlmZmVyZW50IFBDcwpydW4ubGRhLnRyYWluIDwtIEZBTFNFICMgcnVuIGxkYSBvbiB0aGUgdHJhaW5pbmcgc2V0CnJ1bi5wY2FfbGFkLnRlc3QgPC0gVFJVRSAjIGV2YWx1YXRlIHBlcmZvcm1hbmNlIG9uIHRoZSB0ZXN0IHNldApgYGAKClVzaW5nIGNyb3NzLXZhbGlkYXRpb24gb3IgaW5kZXBlbmRlbnQgdGVzdCBzZXQgZXZhbHVhdGlvbiwgd2UgY29tcGFyZSB0aGUgcGVyZm9ybWFuY2Ugb2YgbW9kZWxzIHdpdGggZGlmZmVyZW50IHNwZWNpZmljYXRpb25zLgoKIyMjIFN0ZXAgMjogaW1wb3J0IGRhdGEgYW5kIHRyYWluLXRlc3Qgc3BsaXQKYGBge3J9CiN0cmFpbi10ZXN0IHNwbGl0CmluZm8gPC0gcmVhZC5jc3YodHJhaW5fbGFiZWxfcGF0aCkKbiA8LSBucm93KGluZm8pICNnZXQgbnVtYmVyIG9mIHJvd3MgZnJvbSBjc3YKbl90cmFpbiA8LSByb3VuZChuKig0LzUpLCAwKSAjdXNlIDQvNSBhbW91bnQgb2YgZGF0YSBmb3IgdHJhaW5pbmcKdHJhaW5faWR4IDwtIHNhbXBsZShpbmZvJEluZGV4LCBuX3RyYWluLCByZXBsYWNlID0gRikgI2dyYWIgaW5kZXhlcyB1c2VkIGZvciB0cmFpbmluZwp0ZXN0X2lkeCA8LSBzZXRkaWZmKGluZm8kSW5kZXgsIHRyYWluX2lkeCkgIyBnZXQgaW5kZXhlcyBub3QgdXNlZCBmb3IgdHJhaW5pbmcKYGBgCgpGaWR1Y2lhbCBwb2ludHMgYXJlIHN0b3JlZCBpbiBtYXRsYWIgZm9ybWF0LiBJbiB0aGlzIHN0ZXAsIHdlIHJlYWQgdGhlbSBhbmQgc3RvcmUgdGhlbSBpbiBhIGxpc3QuCgpgYGB7ciByZWFkIGZpZHVjaWFsIHBvaW50c30KI2Z1bmN0aW9uIHRvIHJlYWQgZmlkdWNpYWwgcG9pbnRzCiNpbnB1dDogaW5kZXgKI291dHB1dDogbWF0cml4IG9mIGZpZHVjaWFsIHBvaW50cyBjb3JyZXNwb25kaW5nIHRvIHRoZSBpbmRleApuX2ZpbGVzIDwtIGxlbmd0aChsaXN0LmZpbGVzKHRyYWluX2ltYWdlX2RpciwnKmpwZycpKQpyZWFkTWF0Lm1hdHJpeCA8LSBmdW5jdGlvbihpbmRleCl7CiAgICAgcmV0dXJuKHJvdW5kKHJlYWRNYXQocGFzdGUwKHRyYWluX3B0X2Rpciwgc3ByaW50ZigiJTA0ZCIsIGluZGV4KSwgIi5tYXQiKSlbWzFdXSwwKSkKfQoKaWYgKHJ1bi5wcmVzZW50YXRpb24uZGF5KXsKICB0ZXN0X2lkeCA8LSBjKDE6bl9maWxlcykgI3NhbXBsZShuX2ZpbGVzLCBuX2ZpbGVzLCByZXBsYWNlID0gRikKICBydW4uZ2JtLnRyYWluIDwtIEZBTFNFCiAgcnVuLmZlYXR1cmUudHJhaW4gPC0gRkFMU0UKICBydW4uZ2JtLnRlc3QgPC0gVFJVRQoKICBydW4ucmYgPC0gVFJVRQogIHRyYWluLnJmIDwtIEZBTFNFCiAgdGVzdC5yZiA8LSBUUlVFCn0KCiNsb2FkIGZpZHVjaWFsIHBvaW50cwpmaWR1Y2lhbF9wdF9saXN0IDwtIGxhcHBseSgxOm5fZmlsZXMsIHJlYWRNYXQubWF0cml4KQpzYXZlKGZpZHVjaWFsX3B0X2xpc3QsIGZpbGU9Ii4uL291dHB1dC9maWR1Y2lhbF9wdF9saXN0LlJEYXRhIikKYGBgCgpJZiBvbiBwcmVzZW50YXRpb24gZGF5LCB3ZSB3aWxsIHJ1biBvdXIgYmFzZWxpbmUgYW5kIGFkdmFuY2VkIG1vZGVsLCBhbmQgcHJvZHVjZSBhIGNzdiBmaWxlIGNvbnRhaW5pbmcgbGFiZWwgcHJlZGljdGlvbnMuCgojIyMgU3RlcCAzOiBjb25zdHJ1Y3QgZmVhdHVyZXMgYW5kIHJlc3BvbnNlcwoKKyBUaGUgZm9sbG93IHBsb3RzIHNob3cgaG93IHBhaXJ3aXNlIGRpc3RhbmNlIGJldHdlZW4gZmlkdWNpYWwgcG9pbnRzIGNhbiB3b3JrIGFzIGZlYXR1cmUgZm9yIGZhY2lhbCBlbW90aW9uIHJlY29nbml0aW9uLgoKICArIEluIHRoZSBmaXJzdCBjb2x1bW4sIDc4IGZpZHVjaWFscyBwb2ludHMgb2YgZWFjaCBlbW90aW9uIGFyZSBtYXJrZWQgaW4gb3JkZXIuCiAgKyBJbiB0aGUgc2Vjb25kIGNvbHVtbiBkaXN0cmlidXRpb25zIG9mIHZlcnRpY2FsIGRpc3RhbmNlIGJldHdlZW4gcmlnaHQgcHVwaWwoMSkgYW5kICByaWdodCBicm93IHBlYWsoMjEpIGFyZSBzaG93biBpbiAgaGlzdG9ncmFtcy4gRm9yIGV4YW1wbGUsIHRoZSBkaXN0YW5jZSBvZiBhbiBhbmdyeSBmYWNlIHRlbmRzIHRvIGJlIHNob3J0ZXIgdGhhbiB0aGF0IG9mIGEgc3VycHJpc2VkIGZhY2UuCiAgKyBUaGUgdGhpcmQgY29sdW1uIGlzIHRoZSBkaXN0cmlidXRpb25zIG9mIHZlcnRpY2FsIGRpc3RhbmNlcyBiZXR3ZWVuIHJpZ2h0IG1vdXRoIGNvcm5lcig1MCkKYW5kIHRoZSBtaWRwb2ludCBvZiB0aGUgdXBwZXIgbGlwKDUyKS4gIEZvciBleGFtcGxlLCB0aGUgZGlzdGFuY2Ugb2YgYW4gaGFwcHkgZmFjZSB0ZW5kcyB0byBiZSBzaG9ydGVyIHRoYW4gdGhhdCBvZiBhIHNhZCBmYWNlLgoKIVtGaWd1cmUxXSguLi9maWdzL2ZlYXR1cmVfdmlzdWFsaXphdGlvbi5qcGcpCgpgZmVhdHVyZS5SYCBpcyB0aGUgd3JhcHBlciBmb3IgYWxsIGZlYXR1cmUgZW5naW5lZXJpbmcgZnVuY3Rpb25zIGFuZCBvcHRpb25zLiBUaGUgZnVuY3Rpb24gYGZlYXR1cmUoIClgIGhhdmUgb3B0aW9ucyB0aGF0IGNvcnJlc3BvbmQgdG8gZGlmZmVyZW50IHNjZW5hcmlvcyBmb3IgdGhlIHByb2plY3QgYW5kIHByb2R1Y2VzIGFuIFIgb2JqZWN0IHRoYXQgY29udGFpbnMgZmVhdHVyZXMgYW5kIHJlc3BvbnNlcyB0aGF0IGFyZSByZXF1aXJlZCBieSBhbGwgdGhlIG1vZGVscyB0aGF0IGFyZSBnb2luZyB0byBiZSBldmFsdWF0ZWQgbGF0ZXIuCgogICsgYGZlYXR1cmUuUmAKICArIElucHV0OiBsaXN0IG9mIGltYWdlcyBvciBmaWR1Y2lhbCBwb2ludAogICsgT3V0cHV0OiBhbiBSRGF0YSBmaWxlIHRoYXQgY29udGFpbnMgZXh0cmFjdGVkIGZlYXR1cmVzIGFuZCBjb3JyZXNwb25kaW5nIHJlc3BvbnNlcwoKYGBge3IgZmVhdHVyZX0Kc291cmNlKCIuLi9saWIvZmVhdHVyZS5SIikKdG1fZmVhdHVyZV90cmFpbiA8LSBOQQpnYm1fdG1fZmVhdHVyZV90cmFpbiA8LSBOQQppZihydW4uZmVhdHVyZS50cmFpbil7CiAgdG1fZmVhdHVyZV90cmFpbiA8LSBzeXN0ZW0udGltZShkYXRfdHJhaW48LWZlYXR1cmUoZmlkdWNpYWxfcHRfbGlzdCx0cmFpbl9pZHgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcnVuLnBvbHkuZmVhdHVyZSwgcnVuLmFkZC5wb2x5LmZlYXR1cmUpKQogIGdibV90bV9mZWF0dXJlX3RyYWluIDwtIHN5c3RlbS50aW1lKGdibV9kYXRfdHJhaW48LWZlYXR1cmUoZmlkdWNpYWxfcHRfbGlzdCx0cmFpbl9pZHgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGQUxTRSwgRkFMU0UpKQogIHNhdmUoZ2JtX2RhdF90cmFpbiwgZmlsZT0iLi4vb3V0cHV0L2dibV9mZWF0dXJlX3RyYWluLlJEYXRhIikKICBzYXZlKGRhdF90cmFpbiwgZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfdHJhaW4uUkRhdGEiKQp9ZWxzZXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3RyYWluLlJEYXRhIikKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9nYm1fZmVhdHVyZV90cmFpbi5SRGF0YSIpCn0KCnRtX2ZlYXR1cmVfdGVzdCA8LSBOQQpnYm1fdG1fZmVhdHVyZV90ZXN0IDwtIE5BCmlmKHJ1bi5mZWF0dXJlLnRlc3QpewogIHRtX2ZlYXR1cmVfdGVzdCA8LSBzeXN0ZW0udGltZShkYXRfdGVzdCA8LSBmZWF0dXJlKGZpZHVjaWFsX3B0X2xpc3QsIHRlc3RfaWR4LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJ1bi5wb2x5LmZlYXR1cmUsIHJ1bi5hZGQucG9seS5mZWF0dXJlKSkKICBnYm1fdG1fZmVhdHVyZV90ZXN0IDwtIHN5c3RlbS50aW1lKGdibV9kYXRfdGVzdCA8LSBmZWF0dXJlKGZpZHVjaWFsX3B0X2xpc3QsIHRlc3RfaWR4LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRkFMU0UsIEZBTFNFKSkKICBzYXZlKGdibV9kYXRfdGVzdCwgZmlsZT0iLi4vb3V0cHV0L2dibV9mZWF0dXJlX3Rlc3QuUkRhdGEiKQogIHNhdmUoZGF0X3Rlc3QsIGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3Rlc3QuUkRhdGEiKQp9ZWxzZXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3Rlc3QuUkRhdGEiKQogIGxvYWQoZmlsZT0iLi4vb3V0cHV0L2dibV9mZWF0dXJlX3Rlc3QuUkRhdGEiKQp9CmBgYAoKIyMjIFN0ZXAgNDogdHJhaW4gY2xhc3NpZmljYXRpb24gbW9kZWxzIHdpdGggdHJhaW5pbmcgZmVhdHVyZXMgYW5kIHJlc3BvbnNlczsgcnVuIHRlc3Qgb24gdGVzdCBpbWFnZXMKCkNhbGwgdGhlIHRyYWluIG1vZGVsIGFuZCB0ZXN0IG1vZGVsIGZyb20gbGlicmFyeS4KCmB0cmFpbi5SYCBhbmQgYHRlc3QuUmAgYXJlIHdyYXBwZXJzIGZvciBhbGwgbW9kZWwgdHJhaW5pbmcgc3RlcHMgYW5kIGNsYXNzaWZpY2F0aW9uL3ByZWRpY3Rpb24gc3RlcHMuCgorIGB0cmFpbi5SYAogICsgSW5wdXQ6IGEgZGF0YSBmcmFtZSBjb250YWluaW5nIGZlYXR1cmVzIGFuZCBsYWJlbHMgYW5kIGEgcGFyYW1ldGVyIGxpc3QuCiAgKyBPdXRwdXQ6YSB0cmFpbmVkIG1vZGVsCisgYHRlc3QuUmAKICArIElucHV0OiB0aGUgZml0dGVkIGNsYXNzaWZpY2F0aW9uIG1vZGVsIHVzaW5nIHRyYWluaW5nIGRhdGEgYW5kIHByb2Nlc3NlZCBmZWF0dXJlcyBmcm9tIHRlc3RpbmcgaW1hZ2VzCiAgKyBJbnB1dDogYW4gUiBvYmplY3QgdGhhdCBjb250YWlucyBhIHRyYWluZWQgY2xhc3NpZmllci4KICArIE91dHB1dDogdHJhaW5pbmcgbW9kZWwgc3BlY2lmaWNhdGlvbgoKKyBJbiB0aGlzIFN0YXJ0ZXIgQ29kZSwgd2UgdXNlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gd2l0aCBMQVNTTyBwZW5hbHR5IHRvIGRvIGNsYXNzaWZpY2F0aW9uLgoKYGBge3IgbG9hZGxpYn0Kc291cmNlKCIuLi9saWIvdHJhaW4uUiIpCnNvdXJjZSgiLi4vbGliL3Rlc3QuUiIpCmBgYAoKIyBCYXNlbGluZSBHQk0gTW9kZWwKCiogTW9kZWwgVHJhaW5pbmcKCmBgYHtyfQppZiAocnVuLmdibS50cmFpbil7CiAgaWYgKHNhbXBsZS5yZXdlaWdodCl7CgogICAgZ2JtX2RhdF90cmFpbiRsYWJlbCA8LSBhcy5mYWN0b3IoZ2JtX2RhdF90cmFpbiRsYWJlbCkKICAgIGRhdF90cmFpbl9iYWxhbmNlZF9nYm0gPC0gU01PVEUobGFiZWwgfiAuLCBnYm1fZGF0X3RyYWluLCBwZXJjLm92ZXIgPSAxMDAsIHBlcmMudW5kZXI9MjAwKQogICAgdGFibGUoZGF0X3RyYWluX2JhbGFuY2VkX2dibSRsYWJlbCkKCiAgICBnYm1fdG1fdHJhaW4gPC0gc3lzdGVtLnRpbWUoZ2JtX3RyYWluIDwtIHRyYWluX2dibShkYXRfdHJhaW5fYmFsYW5jZWRfZ2JtLCBzPTAuMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEs9Sywgbj1nYm0ubnVtdHJlZXMsdyA9IE5VTEwpKQoKICB9IGVsc2UgewogICAgZ2JtX3RtX3RyYWluIDwtIHN5c3RlbS50aW1lKGdibV90cmFpbiA8LSB0cmFpbl9nYm0oZ2JtX2RhdF90cmFpbiwgcz0wLjEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBLPUssIG49Z2JtLm51bXRyZWVzLHcgPSBOVUxMKSkKICB9CgogICMgcGxvdCB0aGUgcGVyZm9ybWFuY2UKICBiZXN0Lml0ZXIub29iIDwtIGdibS5wZXJmKGdibV90cmFpbixtZXRob2Q9Ik9PQiIpICMgcmV0dXJucyBvdXQtb2YtYmFnIGVzdGltYXRlZCBiZXN0IG51bWJlciBvZiB0cmVlcwogIHByaW50KGJlc3QuaXRlci5vb2IpCiAgYmVzdC5pdGVyLmN2IDwtIGdibS5wZXJmKGdibV90cmFpbixtZXRob2Q9ImN2IikgIyByZXR1cm5zIEstZm9sZCBjdiBlc3RpbWF0ZSBvZiBiZXN0IG51bWJlciBvZiB0cmVlcwogIHByaW50KGJlc3QuaXRlci5jdikKCiAgc2F2ZVJEUyhnYm1fdHJhaW4sICIuLi9vdXRwdXQvZ2JtX21vZGVsLnJkcyIpCiAgc2F2ZShnYm1fdG1fdHJhaW4sIGJlc3QuaXRlci5jdiwgZmlsZT0iLi4vb3V0cHV0L2dibV9vdXRwdXRzLlJEYXRhIikKfQoKYGBgCgoqIEV2YWx1YXRpb24gb24gVGVzdCBTZXQKCmBgYHtyfQppZihydW4uZ2JtLnRlc3QpewogIGxvYWQoZmlsZT0iLi4vb3V0cHV0L2dibV9vdXRwdXRzLlJEYXRhIikKICBnYm1fdG1fdGVzdCA9IE5BCiAgZmVhdHVyZV90ZXN0IDwtIGFzLm1hdHJpeChnYm1fZGF0X3Rlc3RbLCAxOm5jb2woZ2JtX2RhdF90ZXN0KS0xXSkKCiAgZ2JtX3RyYWluIDwtIHJlYWRSRFMoIi4uL291dHB1dC9nYm1fbW9kZWwucmRzIikKICBnYm1fdG1fdGVzdCA8LSBzeXN0ZW0udGltZShwcm9iX3ByZWRfYmFzZWxpbmU8LXRlc3RfZ2JtKGdibV90cmFpbixhcy5kYXRhLmZyYW1lKGZlYXR1cmVfdGVzdCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuPWJlc3QuaXRlci5jdixwcmVkLnR5cGUgPSAncmVzcG9uc2UnKSkKCiAgbGFiZWxfcHJlZF9iYXNlbGluZSA8LSBjb2xuYW1lcyhwcm9iX3ByZWRfYmFzZWxpbmUpW2FwcGx5KHByb2JfcHJlZF9iYXNlbGluZSwgMSwgd2hpY2gubWF4KV0KfQpgYGAKCiogU2hvdyBHQk0gQWNjdXJhY3kgYW5kIEFVQwoKYGBge3J9CmlmIChydW4uZ2JtLnRlc3QpewogIGxvYWQoZmlsZT0iLi4vb3V0cHV0L2dibV9vdXRwdXRzLlJEYXRhIikKICBnYm1fYWNjdSA8LSBtZWFuKGdibV9kYXRfdGVzdCRsYWJlbCA9PSBsYWJlbF9wcmVkX2Jhc2VsaW5lKQogIGdibS5hdWMgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhsYWJlbF9wcmVkX2Jhc2VsaW5lKSwgZ2JtX2RhdF90ZXN0JGxhYmVsKQogIGdibV9hdWMgPSBXZWlnaHRlZEFVQyhnYm0uYXVjKQogIGNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIGdibSB0cmFpbmluZyBmZWF0dXJlcz0iLCBnYm1fdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQogIGNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIGdibSB0ZXN0aW5nIGZlYXR1cmVzPSIsIGdibV90bV9mZWF0dXJlX3Rlc3RbMV0sICJzIFxuIikKICBjYXQoIlRoZSBBVUMgb2YgZ2JtIG1vZGVsIGlzIiwgZ2JtX2F1YywgIi5cbiIpCiAgY2F0KCJUaGUgYWNjdXJhY3kgb2YgR0JNIGJhc2VsaW5lIG1vZGVsIGlzIiwgZ2JtX2FjY3UqMTAwLCAiJS5cbiIpCiAgY2F0KCJUaW1lIGZvciB0cmFpbmluZyBnYm0gbW9kZWw9IiwgZ2JtX3RtX3RyYWluWzFdLCAicyBcbiIpCiAgY2F0KCJUaW1lIGZvciB0ZXN0aW5nIG1vZGVsPSIsIGdibV90bV90ZXN0WzFdLCAicyBcbiIpCn0KYGBgCgoKCiMgQWR2YW5jZWQgTW9kZWw6IFJhbmRvbSBGb3Jlc3QKClRoZSBzZWNvbmQgYWR2YW5jZWQgbW9kZWwgaXMgcmFuZG9tIGZvcmVzdC4gSGVyZSB3ZSB1c2UgdGhlIGRhdGFzZXRzIHRoYXQgYXJlIGV4dHJhY3RlZCBieSBuZXcgZmVhdHVyZSBmdW5jdGlvbnMuIFdlIHVzZWQgdHdvIG1vZGVscyB0cmFpbmVkIGJ5IGJvdGggaW1iYWxhbmNlZCBhbmQgYmFsYW5jZWQgZGF0YXNldC4gV2UgdXNlZCBTTU9URSBmdW5jdGlvbiB0byBiYWxhbmNlIGJvdGggdHJhaW5pbmcgYW5kIHRlc3RpbmcgZGF0YS4gRm9yIHR1bmluZyB0aGUgbW9kZWwsIHdlIHNldCBtdHJ5ID0gMzA4LCB0cmVlIG51bWJlciA9IDEwMDAsIGFuZCBub2RlIHNpemUgPSAzMCBmb3IgdGhlIFJGIG1vZGVsIHVzaW5nIGJhbGFuY2VkIGRhdGEgKFNNT1RFKSwgYW5kIHdlIHNldCBtdHJ5ID0gMzA4LCB0cmVlIG51bWJlciA9IDE1MDAsIGFuZCBub2RlIHNpemUgPSAzMCBmb3IgdGhlIFJGIG1vZGVsIHVzaW5nIGltYmFsYW5jZWQgZGF0YS4gVGhlIGV2YWx1YXRpb24gb2YgdGhlIG1vZGVsIGlzIHNob3duIGF0IHRoZSBlbmQgb2YgdGhpcyBzZWN0aW9uLiAgCgpUaGUgdHVuaW5nIHBhcnQgaXMgaW4gYSBzZXBhcmF0ZSBmaWxlIG5hbWVkICJhcHBlbmRpeF90dW5lX3JmLnJtZCIgaW4gZG9jIGZvbGRlci4gUGxlYXNlIGZlZWwgZnJlZSB0byBjaGVjayB0aGF0IHRvIHNlZSB0aGUgdHVuaW5nIHByb2Nlc3MuICAKCldlIGFsc28gdHJhaW5lZCByYW5kb20gZm9yZXN0IG1vZGVsIHdpdGggdGhlIGRhdGFzZXRzIHRoYXQgYXJlIGV4dHJhY3RlZCBieSBvbGQgZmVhdHVyZSBmdW5jdGlvbnMuIFRoYXQgaXMgaW4gcGFydCA2LiBUaGFuayB5b3UgZm9yIHJlYWRpbmchCgpgYGB7cn0KaWYocnVuLnJmKXsKICAjIyBUcmFpbmluZyBSRgogIGlmKHRyYWluLnJmKXsKICAgIHJmX2RhdF90cmFpbiA8LSBkYXRfdHJhaW4KICAgIHJmX2RhdF90cmFpbiRsYWJlbCA8LSBhcy5mYWN0b3IocmZfZGF0X3RyYWluJGxhYmVsKQogICAgZGF0X3RyYWluX2JhbGFuY2VkX1NNT1RFIDwtIFNNT1RFKGxhYmVsIH4gLiwgcmZfZGF0X3RyYWluLCBwZXJjLm92ZXIgPSAxMDAsIHBlcmMudW5kZXI9MjAwKQogICAgIyBUcmFpbiBSRiBieSBiYWxhbmNlZCBkYXRhCiAgICB0aW1lLnJmLnRyYWluLmZpbmFsLmJhbGFuY2VkIDwtIHN5c3RlbS50aW1lKAogICAgICByYW5kb21fZm9yZXN0X2ZpdF9maW5hbF9iYWxhbmNlZCA8LSByYW5kb21fZm9yZXN0X3RyYWluKGRhdF90cmFpbl9iYWxhbmNlZF9TTU9URSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gMzA4LCB0cmVlX251bWJlciA9IDEwMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbm9kZV9zaXplID0gMzApKQogICAgc2F2ZShyYW5kb21fZm9yZXN0X2ZpdF9maW5hbF9iYWxhbmNlZCwgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fZmluYWxfYmFsYW5jZWQuUkRhdGEiKQogICAgc2F2ZSh0aW1lLnJmLnRyYWluLmZpbmFsLmJhbGFuY2VkLCBmaWxlID0gIi4uL291dHB1dC9yZl90cmFpbl9maW5hbF90aW1lX2JhbGFuY2VkLlJEYXRhIikKICAgICMgVHJhaW4gUkYgYnkgaW1iYWxhbmNlZCBkYXRhCiAgICB0aW1lLnJmLnRyYWluLmZpbmFsLmltYmFsYW5jZWQgPC0gc3lzdGVtLnRpbWUoCiAgICAgIHJhbmRvbV9mb3Jlc3RfZml0X2ZpbmFsX2ltYmFsYW5jZWQgPC0gcmFuZG9tX2ZvcmVzdF90cmFpbihkYXRfdHJhaW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gMzA4LCB0cmVlX251bWJlciA9IDEwMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBub2RlX3NpemUgPSAzMCkpCiAgICBzYXZlKHRpbWUucmYudHJhaW4uZmluYWwuaW1iYWxhbmNlZCwgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fZmluYWxfdGltZV9pbWJhbGFuY2VkLlJEYXRhIikKICAgIHNhdmUocmFuZG9tX2ZvcmVzdF9maXRfZmluYWxfaW1iYWxhbmNlZCwgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fZmluYWxfaW1iYWxhbmNlZC5SRGF0YSIpCiAgfWVsc2V7CiAgICBsb2FkKCIuLi9vdXRwdXQvcmZfdHJhaW5fZmluYWxfYmFsYW5jZWQuUkRhdGEiKQogICAgbG9hZCgiLi4vb3V0cHV0L3JmX3RyYWluX2ZpbmFsX3RpbWVfYmFsYW5jZWQuUkRhdGEiKQogIH0KICAjIEV2YWx1YXRpb246CiAgaWYodGVzdC5yZil7CiAgICByZl9kYXRfdGVzdCA8LSBkYXRfdGVzdAogICAgcmZfZGF0X3Rlc3QkbGFiZWwgPC0gYXMubnVtZXJpYyhyZl9kYXRfdGVzdCRsYWJlbCkKCiAgICB0aW1lLnJmLnRlc3QuZmluYWwuYmFsYW5jZWQgPC0gc3lzdGVtLnRpbWUoCiAgICAgIHJmX3ByZWRpY3RlZF9iYWxhbmNlZCA8LSBhcy5udW1lcmljKGFzLnZlY3RvcihyYW5kb21fZm9yZXN0X3Rlc3QocmFuZG9tX2ZvcmVzdF9maXRfZmluYWxfYmFsYW5jZWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmZfZGF0X3Rlc3QpKSkpCiAgICByZl9hY2N1cmFjeV9iYWxhbmNlZCA8LSBtZWFuKHJvdW5kKHJmX3ByZWRpY3RlZF9iYWxhbmNlZCA9PSByZl9kYXRfdGVzdCRsYWJlbCkpCiAgICB0cHIuZnByLmJhbGFuY2VkIDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMocmZfcHJlZGljdGVkX2JhbGFuY2VkKSxyZl9kYXRfdGVzdCRsYWJlbCkKICAgIHJmX0FVQ19iYWxhbmNlZCA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLmJhbGFuY2VkKQoKICAgIGNhdCgiQVVDIGZvciB0dW5lZCBSYW5kb20gRm9yZXN0KGJhbGFuY2VkKTogIiwgcmZfQVVDX2JhbGFuY2VkLCIuXG4iKQogICAgY2F0KCJBY2N1cmFjeSBmb3IgdHVuZWQgUmFuZG9tIEZvcmVzdChiYWxhbmNlZCkiLCByZl9hY2N1cmFjeV9iYWxhbmNlZCoxMDAsIiUuXG4iKQogICAgY2F0KCJUcmFpbmluZyB0aW1lIGZvciB0dW5lZCBSYW5kb20gRm9yZXN0OiAiLCB0aW1lLnJmLnRyYWluLmZpbmFsLmJhbGFuY2VkWzFdLCAicy5cbiIpCiAgICBjYXQoIlRlc3RpbmcgdGltZSBmb3IgdHVuZWQgUmFuZG9tIEZvcmVzdDogIiwgdGltZS5yZi50ZXN0LmZpbmFsLmJhbGFuY2VkWzFdLCAicy5cbiIpCiAgICBjYXQoIiAgICIsIlxuIikKICB9Cn0KYGBgCgpXZSB0aGluayBSRiBtb2RlbCBkbyBub3QgbmVlZCBDcm9zcy1WYWxpZGF0aW9uLiBIZXJlIGlzIGEgc25pcHBldCBmcm9tIEJyZWltYW4ncyBvZmZpY2lhbCBkb2N1bWVudGF0aW9uOgpJbiByYW5kb20gZm9yZXN0cywgdGhlcmUgaXMgbm8gbmVlZCBmb3IgY3Jvc3MtdmFsaWRhdGlvbiBvciBhIHNlcGFyYXRlIHRlc3Qgc2V0IHRvIGdldCBhbiB1bmJpYXNlZCBlc3RpbWF0ZSBvZiB0aGUgdGVzdCBzZXQgZXJyb3IuIEl0IGlzIGVzdGltYXRlZCBpbnRlcm5hbGx5LCBkdXJpbmcgdGhlIHJ1biwgYXMgZm9sbG93czogRWFjaCB0cmVlIGlzIGNvbnN0cnVjdGVkIHVzaW5nIGEgZGlmZmVyZW50IGJvb3RzdHJhcCBzYW1wbGUgZnJvbSB0aGUgb3JpZ2luYWwgZGF0YS4gQWJvdXQgb25lLXRoaXJkIG9mIHRoZSBjYXNlcyBhcmUgbGVmdCBvdXQgb2YgdGhlIGJvb3RzdHJhcCBzYW1wbGUgYW5kIG5vdCB1c2VkIGluIHRoZSBjb25zdHJ1Y3Rpb24gb2YgdGhlIGt0aCB0cmVlLiBQdXQgZWFjaCBjYXNlIGxlZnQgb3V0IGluIHRoZSBjb25zdHJ1Y3Rpb24gb2YgdGhlIGt0aCB0cmVlIGRvd24gdGhlIGt0aCB0cmVlIHRvIGdldCBhIGNsYXNzaWZpY2F0aW9uLiBJbiB0aGlzIHdheSwgYSB0ZXN0IHNldCBjbGFzc2lmaWNhdGlvbiBpcyBvYnRhaW5lZCBmb3IgZWFjaCBjYXNlIGluIGFib3V0IG9uZS10aGlyZCBvZiB0aGUgdHJlZXMuIEF0IHRoZSBlbmQgb2YgdGhlIHJ1biwgdGFrZSBqIHRvIGJlIHRoZSBjbGFzcyB0aGF0IGdvdCBtb3N0IG9mIHRoZSB2b3RlcyBldmVyeSB0aW1lIGNhc2UgbiB3YXMgb29iLiBUaGUgcHJvcG9ydGlvbiBvZiB0aW1lcyB0aGF0IGogaXMgbm90IGVxdWFsIHRvIHRoZSB0cnVlIGNsYXNzIG9mIG4gYXZlcmFnZWQgb3ZlciBhbGwgY2FzZXMgaXMgdGhlIG9vYiBlcnJvciBlc3RpbWF0ZS4gVGhpcyBoYXMgcHJvdmVuIHRvIGJlIHVuYmlhc2VkIGluIG1hbnkgdGVzdHMuCgoKCgojIEFsdGVybmF0aXZlIE1vZGVsIDE6IFNWTSBNb2RlbAoKKiBCYWxhbmNlIHRoZSBUcmFpbmluZyBTZXQKCklmIHRoZSBnaXZlbiBkYXRhIHNldCBpcyBpbWJhbGFuY2VkLCB3ZSBhcHBseSBTTU9URSB0byByZWJhbGFuY2UgdGhlIGRhdGEgYW5kIHVzZSBpdCB0byB0cmFpbiBvdXIgc3ZtIG1vZGVscy4KCmBgYHtyfQppZihydW4uc3ZtKXsKICBpZihydW4uc3ZtLnRyYWluKXsKICAgIHRtX3N2bV9yZWJhbGFuY2VkX3RyYWluIDwtIE5BCiAgICBpZihzYW1wbGUucmV3ZWlnaHQpewogICAgICBkYXRfdHJhaW4kbGFiZWwgPSBhcy5mYWN0b3IoZGF0X3RyYWluJGxhYmVsKQogICAgICB0bV9zdm1fcmViYWxhbmNlZF90cmFpbiA8LSBzeXN0ZW0udGltZShzdm1fdHJhaW5pbmdfZGF0YSA8LSBTTU9URShsYWJlbCB+IC4sIGRhdGEgPSBkYXRfdHJhaW4pKQogICAgICBzYXZlKHRtX3N2bV9yZWJhbGFuY2VkX3RyYWluLCBmaWxlPSIuLi9vdXRwdXQvdG1fc3ZtX3JlYmFsYW5jZWRfdHJhaW4uUkRhdGEiKQogICAgfSBlbHNlIHsKICAgICAgc3ZtX3RyYWluaW5nX2RhdGEgPC0gZGF0X3RyYWluCiAgICAgIHRtX3N2bV9yZWJhbGFuY2VkX3RyYWluIDwtIHRtX2ZlYXR1cmVfdHJhaW4KICAgIH0KICB9Cn0KYGBgCgoqIE1vZGVsIFNlbGVjdGlvbgoKVHVuaW5nIGh5cGVyLXBhcmFtZXRlcnMgZm9yIGJvdGggbGluZWFyIGFuZCByYWRpYWwgYmFzaXMga2VybmVsIGFuZCBzZWxlY3QgdGhlIGtlcm5lbCBtZXRob2QgdGhhdCBwcm9kdWNlcyB0aGUgaGlnaGVzdCBBVUMgYW5kIGFjY3VyYWN5IGFtb25nIHRoZSB0d28gbWV0aG9kcy4KCmBgYHtyfQppZihydW4uc3ZtKXsKICBzZXQuc2VlZCgyMDIwKQogIGlmKG1vZGVsLnNlbGVjdGlvbil7CiAgICBzdm1fbW9kZWxfYXVjIDwtIHJlcChOQSwgMikKICAgIHN2bV9tb2RlbF9hY2N1IDwtIHJlcChOQSwgMikKICAgICMjIyBsaW5lYXIga2VybmVsCiAgICBpZihydW4uY3YpewogICAgICBiZXN0LmxpbmVhci5jb3N0IDwtIHN2bV9saW5lYXJfY29zdF90dW5lKHN2bV90cmFpbmluZ19kYXRhKQogICAgICBjYXQoIlRoZSBiZXN0IGNvc3QgZm9yIHN2bSBtb2RlbCB3aXRoIGxpbmVhciBrZXJuZWwgaXM6ICIsCiAgICAgICAgICBiZXN0LmxpbmVhci5jb3N0JGJlc3QucGFyYW1ldGVycyRjb3N0KSAjIGJlc3QgY29zdCBpcyAwLjAxCiAgICAgIHN2bS5saW5lYXIudHJhaW4uc3RhcnQgPSBwcm9jLnRpbWUoKQogICAgICBzdm1fbGluZWFyX21vZCA8LSBzdm1fbGluZWFyX3RyYWluKHN2bV90cmFpbmluZ19kYXRhLCAwLjAxLCBLKQogICAgICBzdm0ubGluZWFyLnRyYWluLmVuZCA9IHByb2MudGltZSgpCiAgICAgIHN2bS5saW5lYXIudG0gPSBzdm0ubGluZWFyLnRyYWluLmVuZCAtIHN2bS5saW5lYXIudHJhaW4uc3RhcnQKICAgICAgc2F2ZShzdm1fbGluZWFyX21vZCwgZmlsZT0iLi4vb3V0cHV0L3N2bV9saW5lYXJfbW9kLlJEYXRhIikKICAgICAgc2F2ZShzdm0ubGluZWFyLnRtLCBmaWxlPSIuLi9vdXRwdXQvdG1fc3ZtX2xpbmVhcl9tb2QuUkRhdGEiKQogICAgfSBlbHNlIHsKICAgICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvc3ZtX2xpbmVhcl9tb2QuUkRhdGEiKQogICAgfQogICAgc3ZtX2xpbmVhcl9wcmVkIDwtIHN2bV90ZXN0KHN2bV9saW5lYXJfbW9kLCBzdm1fdHJhaW5pbmdfZGF0YSwgRkFMU0UpCiAgICAjIGV2YWx1YXRlIHBlcmZvcm1hbmNlIG9uIGxpbmVhciBrZXJuZWwKICAgIHN2bV9tb2RlbF9hY2N1WzFdIDwtIG1lYW4ocm91bmQoc3ZtX2xpbmVhcl9wcmVkID09IHN2bV90cmFpbmluZ19kYXRhJGxhYmVsKSkKICAgIHRwci5mcHJfbGluZWFyIDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMoc3ZtX2xpbmVhcl9wcmVkKSwgc3ZtX3RyYWluaW5nX2RhdGEkbGFiZWwpCiAgICBzdm1fbW9kZWxfYXVjWzFdIDwtIFdlaWdodGVkQVVDKHRwci5mcHJfbGluZWFyKQoKICAgICMjIyMgcmFkaWFsIGJhc2lzIGtlcm5lbAogICAgaWYocnVuLmN2KXsKICAgICAgYmVzdC5yYWRpYWwuY29zdCA8LSBzdm1fcmFkaWFsX2Nvc3RfdHVuZShzdm1fdHJhaW5pbmdfZGF0YSkKICAgICAgc3ZtLnJiZi50cmFpbi5zdGFydCA9IHByb2MudGltZSgpCiAgICAgIHN2bV9yYWRpYWxfbW9kIDwtIHN2bV9yYWRpYWxfdHJhaW4oc3ZtX3RyYWluaW5nX2RhdGEsIGJlc3QucmFkaWFsLmNvc3QsIEspCiAgICAgIHN2bS5yYmYudHJhaW4uZW5kID0gcHJvYy50aW1lKCkKICAgICAgc3ZtLnJiZi50bSA9IHN2bS5yYmYudHJhaW4uZW5kIC0gc3ZtLnJiZi50cmFpbi5zdGFydAogICAgICBzYXZlKHN2bV9yYWRpYWxfbW9kLCBmaWxlPSIuLi9vdXRwdXQvc3ZtX3JhZGlhbF9tb2QuUkRhdGEiKQogICAgICBzYXZlKHN2bS5yYmYudG0sIGZpbGU9Ii4uL291dHB1dC90bV9zdm1fcmFkaWFsX21vZC5SRGF0YSIpCiAgICB9IGVsc2UgewogICAgICBsb2FkKGZpbGU9Ii4uL291dHB1dC9zdm1fcmFkaWFsX21vZC5SRGF0YSIpCiAgICB9CiAgICBzdm1fcmFkaWFsX3ByZWQgPC0gc3ZtX3Rlc3Qoc3ZtX3JhZGlhbF9tb2QsIHN2bV90cmFpbmluZ19kYXRhLCBGQUxTRSkKICAgICMgZXZhbHVhdGUgcGVyZm9ybWFuY2Ugb24gcmJmIGtlcm5lbAogICAgc3ZtX21vZGVsX2FjY3VbMl0gPC0gbWVhbihyb3VuZChzdm1fcmFkaWFsX3ByZWQgPT0gc3ZtX3RyYWluaW5nX2RhdGEkbGFiZWwpKQogICAgdHByLmZwcl9yYmYgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhzdm1fcmFkaWFsX3ByZWQpLCBzdm1fdHJhaW5pbmdfZGF0YSRsYWJlbCkKICAgIHN2bV9tb2RlbF9hdWNbMl0gPC0gV2VpZ2h0ZWRBVUModHByLmZwcl9yYmYpCgogICAgIyB0YWJsZSB0byBkaXNwbGF5IHJlc3VsdHMgZm9yIHRoZSB0d28ga2VybmVsIG1ldGhvZHMKICAgIHN2bV9yZXMgPSBtYXRyaXgocmVwKE5BLDYpLG5jb2w9MykKICAgIHN2bV9yZXNbLDFdID0gc3ZtX21vZGVsX2FjY3UKICAgIHN2bV9yZXNbLDJdID0gc3ZtX21vZGVsX2F1YwogICAgc3ZtX3Jlc1ssM10gPSBjKHN2bS5saW5lYXIudG1bWzNdXSwgc3ZtLnJiZi50bVtbM11dKQogICAgY29sbmFtZXMoc3ZtX3JlcykgPSBjKCJBY2N1cmFjeSIsICJBVUMiLCJSdW5uaW5nIFRpbWUiKQogICAgcm93bmFtZXMoc3ZtX3JlcykgPSBjKCJsaW5lYXIiLCJyYWRpYWwgYmFzaXMiKQogICAgc2F2ZShzdm1fcmVzLCBmaWxlPSIuLi9vdXRwdXQvc3ZtX21vZGVsX3NlbGVjdGlvbi5SRGF0YSIpCiAgfSBlbHNlIHsKICAgIGlmKHJ1bi5wcmVzZW50YXRpb24uZGF5KXsKICAgICAgdG1fc3ZtX3JhZGlhbF9tb2QgPCBzeXN0ZW0udGltZShzdm1fcmFkaWFsX21vZCA8LSBzdm1fcmFkaWFsX3RyYWluKHN2bV90cmFpbmluZ19kYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMSwgSykpCiAgICB9IGVsc2UgewogICAgICBsb2FkKGZpbGU9Ii4uL291dHB1dC9zdm1fcmFkaWFsX21vZC5SRGF0YSIpCiAgICAgIGxvYWQoZmlsZT0iLi4vb3V0cHV0L3N2bV9tb2RlbF9zZWxlY3Rpb24uUkRhdGEiKQogICAgICBzdm1fcmVzCiAgICB9CiAgfQp9CmBgYAoKClNpbmNlIHJhZGlhbCBiYXNpcyBrZXJuZWwgaGFzIGhpZ2hlciBhY2N1cmFjeSBhbmQgQVVDIHRoYW4gbGluZWFyIGtlcm5lbCwgd2Ugd2lsbCBjaG9vc2UgcmFkaWFsIGJhc2lzIGFzIG91ciBrZXJuZWwgbWV0aG9kIGZvciB0cmFpbmluZyB0aGUgc3ZtIG1vZGVsLgoKICAqIEV2YWx1YXRpb24gb24gVGVzdGluZyBEYXRhCgpgYGB7cn0KaWYocnVuLnN2bSl7CiAgdG1fc3ZtX3JiZl90ZXN0IDwtIE5BCiAgc3ZtX3Rlc3RpbmdfZGF0YSA8LSBkYXRfdGVzdAogIGlmKHJ1bi5zdm0udGVzdCl7CiAgICBpZighcnVuLnByZXNlbnRhdGlvbi5kYXkpIHsKICAgICAgdG1fc3ZtX3JiZl90ZXN0IDwtIHN5c3RlbS50aW1lKHN2bV9yYmZfcHJlZCA8LSBzdm1fdGVzdChzdm1fcmFkaWFsX21vZCwgc3ZtX3Rlc3RpbmdfZGF0YSwgRkFMU0UpKQogICAgICBzdm1fdGVzdF9hY2N1ID0gbWVhbihyb3VuZChzdm1fcmJmX3ByZWQgPT0gc3ZtX3Rlc3RpbmdfZGF0YSRsYWJlbCkpCiAgICAgIHRwci5mcHIucmJmIDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMoc3ZtX3JiZl9wcmVkKSwgc3ZtX3Rlc3RpbmdfZGF0YSRsYWJlbCkKICAgICAgc3ZtX3Rlc3RfYXVjID0gV2VpZ2h0ZWRBVUModHByLmZwci5yYmYpCiAgICAgIHNhdmUodG1fc3ZtX3JiZl90ZXN0LCBmaWxlPSIuLi9vdXRwdXQvdG1fc3ZtX3JiZl90ZXN0LlJEYXRhIikKICAKICAgICAgY2F0KCJUaGUgYWNjdXJhY3kgb2Ygc3ZtIG1vZGVsIGlzIiwgc3ZtX3Rlc3RfYWNjdSoxMDAsICIlLlxuIikKICAgICAgY2F0KCJUaGUgQVVDIG9mIHN2bSBtb2RlbCBpcyIsIHN2bV90ZXN0X2F1YywgIi5cbiIpCiAgICB9IGVsc2UgewogICAgICB0bV9zdm1fcmJmX3Rlc3QgPC0gc3lzdGVtLnRpbWUoc3ZtX3JiZl9wcmVkIDwtIHN2bV90ZXN0KHN2bV9yYWRpYWxfbW9kLCBzdm1fdGVzdGluZ19kYXRhKSkKICAgICAgc3ZtX3Rlc3RfYWNjdSA9IG1lYW4ocm91bmQoc3ZtX3JiZl9wcmVkID09IHN2bV90ZXN0aW5nX2RhdGEkbGFiZWwpKQogICAgICB0cHIuZnByLnJiZiA8LSBXZWlnaHRlZFJPQyhhcy5udW1lcmljKHN2bV9yYmZfcHJlZCksIHN2bV90ZXN0aW5nX2RhdGEkbGFiZWwpCiAgICAgIHN2bV90ZXN0X2F1YyA9IFdlaWdodGVkQVVDKHRwci5mcHIucmJmKQogICAgICBzYXZlKHRtX3N2bV9yYmZfdGVzdCwgZmlsZT0iLi4vb3V0cHV0L3RtX3N2bV9yYmZfdGVzdC5SRGF0YSIpCiAgCiAgICAgIGNhdCgiVGhlIGFjY3VyYWN5IG9mIHN2bSBtb2RlbCBpcyIsIHN2bV90ZXN0X2FjY3UqMTAwLCAiJS5cbiIpCiAgICAgIGNhdCgiVGhlIEFVQyBvZiBzdm0gbW9kZWwgaXMiLCBzdm1fdGVzdF9hdWMsICIuXG4iKQogICAgfQogIH0KfQpgYGAKCgoqIFN1bW1hcml6ZSBSdW5uaW5nIFRpbWUKCmBgYHtyfQppZihydW4uc3ZtKXsKICBjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdCB0cmFpbmluZyBmZWF0dXJlcyA9IiwgdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQogIGNhdCgiVGltZSBmb3IgY29uc3RydWN0IHRlc3RpbmcgZmVhdHVyZXMgPSIsIHRtX2ZlYXR1cmVfdGVzdFsxXSwgInMgXG4iKQogIGNhdCgiVGltZSBmb3IgdHJhaW5pbmcgc3ZtIG1vZGVsID0iLCBzdm1fcmVzWzIsM10sICJzIFxuIikKICBjYXQoIlRpbWUgZm9yIHRlc3Rpbmcgc3ZtIG1vZGVsPSIsIHRtX3N2bV9yYmZfdGVzdFsxXSwgInMgXG4iKQp9CmBgYAoKCgojIEFsdGVybmF0aXZlIE1vZGVsIDI6IFJpZGdlIE1vZGVsCgoqIEFwcGx5IENvbnN0cnVjdGVkIFJpZGdlIE1vZGVsIHRvIHRoZSBUcmFpbmluZyBTZXQKCmBgYHtyfQppZihydW4ucmlkZ2UpewogIHRtX3JpZGdlX3RyYWluIDwtIE5BCiAgaWYgKHRyYWluLnJpZGdlKXsKICAgIGRhdF90cmFpbl9yZWJhbGFuY2VkIDwtIFJPU0UobGFiZWwgfiAuLCBkYXRhID0gZGF0X3RyYWluLCBzZWVkPTIwMjApJGRhdGEKICAgIHRtX3JpZGdlX3RyYWluIDwtIHN5c3RlbS50aW1lKHJpZGdlX2N2X21vZGVsPC1yaWRnZV90cmFpbih0cmFpbl9kYXRhPWRhdF90cmFpbl9yZWJhbGFuY2VkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhPWFscGhhLCBLPUssIGxhbWJkYT1sYW1iZGEpKQogICAgc2F2ZShyaWRnZV9jdl9tb2RlbCwgZmlsZT0iLi4vb3V0cHV0L3JpZGdlX2N2X21vZGVsLlJEYXRhIikKICAgIHNhdmUodG1fcmlkZ2VfdHJhaW4sIGZpbGU9Ii4uL291dHB1dC9yaWRnZV90cmFpbl90aW1lLlJEYXRhIikKICB9IGVsc2UgewogICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvcmlkZ2VfY3ZfbW9kZWwuUkRhdGEiKQogICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvcmlkZ2VfdHJhaW5fdGltZS5SRGF0YSIpCiAgfQp9CmBgYAoKKiBVc2UgQ3Jvc3MtVmFsaWRhdGlvbiB0byBDaG9vc2UgdGhlIE9wdGltYWwgTGFtYmRhIHdpdGggU21hbGxlc3QgTVNFCgpgYGB7cn0KaWYocnVuLnJpZGdlKXsKICBpZiAodHJhaW4ucmlkZ2UpewogICAgc2V0LnNlZWQoMjAyMCkKICAgIGRhdF90cmFpbl9yZWJhbGFuY2VkIDwtIFJPU0UobGFiZWwgfiAuLCBkYXRhID0gZGF0X3RyYWluLCBzZWVkPTIwMjApJGRhdGEKICAgIGZlYXR1cmVfdHJhaW4gPSBhcy5tYXRyaXgoZGF0X3RyYWluX3JlYmFsYW5jZWRbLC1kaW0oZGF0X3RyYWluX3JlYmFsYW5jZWQpWzJdXSkKICAgIGxhYmVsX3RyYWluID0gYXMuaW50ZWdlcihkYXRfdHJhaW5fcmViYWxhbmNlZCRsYWJlbCkKICAgIHJpZGdlX21vZGVsID0gY3YuZ2xtbmV0KHg9ZmVhdHVyZV90cmFpbiwgeT1sYWJlbF90cmFpbiwgYWxwaGE9YWxwaGEsIG5mb2xkcz1LLCBsYW1iZGE9bGFtYmRhKQogICAgb3B0X2xhbWJkYSA9IHJpZGdlX21vZGVsJGxhbWJkYS5taW4KICAgIHNhdmUob3B0X2xhbWJkYSwgZmlsZT0iLi4vb3V0cHV0L3JpZGdlX29wdGltYWxfbGFtYmRhLlJEYXRhIikKICB9IGVsc2UgewogICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvcmlkZ2Vfb3B0aW1hbF9sYW1iZGEuUkRhdGEiKQogIH0KfQpgYGAKCiogUHJlZGljdCBvbiBUZXN0aW5nIFNldCB3aXRoIHRoZSBPcHRpbWFsIExhbWJkYQoKYGBge3J9CmlmKHJ1bi5yaWRnZSl7CiAgdG1fcmlkZ2VfdGVzdCA9IE5BCiAgaWYocnVuLnRlc3QpewogICAgbG9hZCgiLi4vb3V0cHV0L3JpZGdlX2N2X21vZGVsLlJEYXRhIikKICAgIGRhdF90ZXN0X3JlYmFsYW5jZWQgPC0gZGF0X3Rlc3QKICAgIGZlYXR1cmVfdGVzdCA8LSBhcy5tYXRyaXgoZGF0X3Rlc3RfcmViYWxhbmNlZFssIC1kaW0oZGF0X3Rlc3RfcmViYWxhbmNlZClbMl1dKQogICAgdG1fcmlkZ2VfdGVzdCA8LSBzeXN0ZW0udGltZShsYWJlbF9wcmVkPC1hcy5pbnRlZ2VyKHJpZGdlX3Rlc3QobW9kZWw9cmlkZ2VfY3ZfbW9kZWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmZWF0dXJlcz1mZWF0dXJlX3Rlc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVkLnR5cGUgPSAnY2xhc3MnKSkpCiAgICBzYXZlKHRtX3JpZGdlX3Rlc3QsIGZpbGU9Ii4uL291dHB1dC9yaWRnZV90ZXN0X3RpbWUuUkRhdGEiKQogIH0gZWxzZXsKICAgIGxvYWQoZmlsZT0iLi4vb3V0cHV0L3JpZGdlX3Rlc3RfdGltZS5SRGF0YSIpCiAgfQp9CmBgYAoKKiBTdW1tYXJpemUgUnVubmluZyBUaW1lCgpgYGB7cn0KaWYocnVuLnJpZGdlKXsKICBjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0cmFpbmluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3RyYWluWzFdLCAicyBcbiIpCiAgY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdGVzdGluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3Rlc3RbMV0sICJzIFxuIikKICBjYXQoIlRpbWUgZm9yIHRyYWluaW5nIHJpZGdlIG1vZGVsPSIsIHRtX3JpZGdlX3RyYWluWzFdLCAicyBcbiIpCiAgY2F0KCJUaW1lIGZvciB0ZXN0aW5nIHJpZGdlIG1vZGVsPSIsIHRtX3JpZGdlX3Rlc3RbMV0sICJzIFxuIikKfQpgYGAKCiogRXZhbHVhdGlvbiBvbiBJbmRlcGVuZGVudCBUZXN0aW5nIERhdGEKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQppZihydW4ucmlkZ2UpewogIGxvYWQoIi4uL291dHB1dC9yaWRnZV9jdl9tb2RlbC5SRGF0YSIpCiAgZGF0X3Rlc3RfcmViYWxhbmNlZCA8LSBkYXRfdGVzdAogIGZlYXR1cmVfdGVzdCA8LSBhcy5tYXRyaXgoZGF0X3Rlc3RfcmViYWxhbmNlZFssIC1kaW0oZGF0X3Rlc3RfcmViYWxhbmNlZClbMl1dKQogIGxhYmVsX3ByZWQgPSBhcy5pbnRlZ2VyKHByZWRpY3QocmlkZ2VfY3ZfbW9kZWwsIHM9b3B0X2xhbWJkYSwgbmV3eD1mZWF0dXJlX3Rlc3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlPSdjbGFzcycpKQogIGxhYmVsX3Rlc3QgPSBhcy5pbnRlZ2VyKGRhdF90ZXN0X3JlYmFsYW5jZWQkbGFiZWwpCiAgcmlkZ2VfYWNjdXJhY3kgPSBtZWFuKHJvdW5kKGxhYmVsX3Rlc3Q9PSBsYWJlbF9wcmVkKSkKICBjYXQoIlRoZSBhY2N1cmFjeSBvZiB0aGUgcmlkZ2UgbW9kZWwgaXMiLCByaWRnZV9hY2N1cmFjeSoxMDAsICIlLlxuIikKICByaWRnZV9BVUMgPSBhdWMocm9jKGxhYmVsX3ByZWQsbGFiZWxfdGVzdCkpCiAgY2F0KCJUaGUgQVVDIG9mIHRoZSByaWRnZSBtb2RlbCBpcyIsIHJpZGdlX0FVQywgIi5cbiIpCn0KYGBgCgoKCiMgQWx0ZXJuYXRpdmUgTW9kZWwgMzogUENBICsgTERBCgoqIFJlYmFsYW5jZSBUcmFpbmluZyBTZXQKCmBgYHtyfQppZihydW4ucGNhX2xkYSl7CiAgaWYocnVuLmxkYS50cmFpbil7CiAgICBpZihzYW1wbGUucmV3ZWlnaHQpewogICAgICBkYXRfdHJhaW4kbGFiZWwgPC0gYXMuZmFjdG9yKGRhdF90cmFpbiRsYWJlbCkKICAgICAgYmFsYW5jZWRfdHJhaW5fZGF0YSA8LSBTTU9URShsYWJlbH4uLGRhdGEgPSBkYXRfdHJhaW4pCiAgICAgIHNhdmUoYmFsYW5jZWRfdHJhaW5fZGF0YSwgZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfYmFsYW5jZWRfdHJhaW4uUkRhdGEiKQogICAgfSBlbHNlIHsKICAgICAgbG9hZChiYWxhbmNlZF90cmFpbl9kYXRhLCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90cmFpbi5SRGF0YSIpCiAgICB9CiAgfQp9CmBgYAoKKiBQZXJmb3JtIFBDQSBmb3IgRGltZW5zaW9uIFJlZHVjdGlvbgoKKipTaW5jZSB0aGVyZSBhcmUgb3ZlciA2MDAwIGZlYXR1cmVzLCB3ZSBpbXBsZW1lbnQgdGhlIFBDQSBtZXRob2QgdG8gcmVkdWNlIGRpbWVuc2lvbiBhY2NvcmRpbmcgdG8gdGhlIGNvdmFyaWFuY2UgbWF0cml4LiBXZSBvbmx5IHJldGFpbiBQQ3Mgd2l0aCBsYXJnZSB2YXJpYW5jZS4qKgoKYGBge3IgcGNhIGxkYX0KaWYocnVuLnBjYV9sZGEpewogIGJhbGFuY2VkX3Rlc3RfZGF0YSA8LSBkYXRfdGVzdAogIGlmKHJ1bi5zZWxlY3RfUEMpewogICAgI3NlcGFyYXRlIHRoZSBmZWF0dXJlcyBmcm9tIGxhYmVsCiAgICBkYXRfdHJhaW5fbmV3IDwtIGJhbGFuY2VkX3RyYWluX2RhdGFbLC1kaW0oYmFsYW5jZWRfdHJhaW5fZGF0YSlbMl1dCiAgICBkYXRfdGVzdF9uZXcgPC0gYmFsYW5jZWRfdGVzdF9kYXRhWywtZGltKGJhbGFuY2VkX3Rlc3RfZGF0YSlbMl1dCiAgICAjY3JlYXRlIGEgdmVjdG9yIGNvbnRhaW4gdGFyZ2V0IG51bWJlciBvZiBQQ3MKICAgIG51bS5wY2EgPC0gYygxMCw1MCw1MDAsMTAwMCkKICAgIHRyYWluX3BjYSA8LSBmdW5jdGlvbihudW0ucGNhKXsKICAgICAgZm9yKGkgaW4gMTpsZW5ndGgobnVtLnBjYSkpewogICAgICAgICNzdGFydCB0aW1lIGZvciB0cmFpbmluZyB0aGUgbW9kZWwKICAgICAgICB0cmFpbi5tb2RlbC5zdGFydCA9IHByb2MudGltZSgpCiAgICAgICAgI3J1biBQQ0EKICAgICAgICBwY2EgPC0gcHJjb21wKGRhdF90cmFpbl9uZXcpCiAgICAgICAgI3N0b3JlIGZvciBlYWNoIHBvdGVudGlhbCBQQwogICAgICAgIHRyYWluX3BjYSA8LSBkYXRhLmZyYW1lKHBjYSR4WywxOm51bS5wY2FbaV1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gYmFsYW5jZWRfdHJhaW5fZGF0YVtkaW0oYmFsYW5jZWRfdHJhaW5fZGF0YSlbMl1dKQogICAgICAgIHByZWRfcGNhIDwtIHByZWRpY3QocGNhLGRhdF90ZXN0X25ldykKICAgICAgICB0ZXN0X3BjYSA8LSBkYXRhLmZyYW1lKHByZWRfcGNhWywxOm51bS5wY2FbaV1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBiYWxhbmNlZF90ZXN0X2RhdGFbZGltKGJhbGFuY2VkX3Rlc3RfZGF0YSlbMl1dKQogICAgICAgICNmaXR0aW5nIHRoZSBsZGEgbW9kZWwKICAgICAgICBsZGFfcGNhIDwtIGxkYShsYWJlbCB+IC4sIGRhdGEgPSB0cmFpbl9wY2EpCiAgICAgICAgI3N0b3AgdGltZSBmb3IgdHJhaW5pbmcgdGhlIG1vZGVsCiAgICAgICAgdHJhaW4ubW9kZWwuZW5kID0gcHJvYy50aW1lKCkKICAgICAgICAjc3RhcnQgdGltZSBmb3IgdGVzdGluZyB0aGUgbW9kZWwKICAgICAgICB0ZXN0Lm1vZGVsLnN0YXJ0ID0gcHJvYy50aW1lKCkKICAgICAgICAjcHJlZGljdCBsZGEgbW9kZWwKICAgICAgICBsZGFfcHJlZF9wY2EgPSBwcmVkaWN0KGxkYV9wY2EsdGVzdF9wY2FbLWRpbSh0ZXN0X3BjYSlbMl1dKQogICAgICAgICNlbmQgdGltZSBmb3IgdGVzdGluZyB0aGUgbW9kZWwKICAgICAgICB0ZXN0Lm1vZGVsLmVuZCA9IHByb2MudGltZSgpCiAgICAgICAgI3Rlc3QgYWNjdXJhY3kKICAgICAgICB0ZXN0X2FjY3VyYWN5PWNvbmZ1c2lvbk1hdHJpeChsZGFfcHJlZF9wY2EkY2xhc3MsIHRlc3RfcGNhJGxhYmVsKSRvdmVyYWxsWzFdCiAgICAgICAgcHJpbnQobGlzdChsMT10cmFpbi5tb2RlbC5lbmQgLSB0cmFpbi5tb2RlbC5zdGFydCwKICAgICAgICAgICAgICAgICAgIGwyPXRlc3QubW9kZWwuZW5kIC0gdGVzdC5tb2RlbC5zdGFydCwKICAgICAgICAgICAgICAgICAgIGwzPXRlc3RfYWNjdXJhY3kpKQogICAgICB9CiAgICB9CiAgICB0cmFpbl9wY2EobnVtLnBjYSkKICB9Cn0KYGBgCgoqKkJ5IGNvbXBhcmluZyB0aGUgdHJhaW5pbmcgdGltZSwgdGVzdCB0aW1lIGFuZCBhY2N1cmFjeSwgd2UgdXNlIG1vZGVsIHdpdGggNTAwIFBDcy4qKgoKICAqIE1vZGVsIFRyYWluaW5nCgpgYGB7ciBmaW5hbCBtb2RlbH0KaWYocnVuLnBjYV9sZGEpewogIHRyYWluLm1vZGVsLnN0YXJ0ID0gcHJvYy50aW1lKCkKICBpZihydW4ubGRhLnRyYWluKXsKICAgIHBjYV81MDAgPC0gcHJjb21wKGJhbGFuY2VkX3RyYWluX2RhdGFbLC1kaW0oYmFsYW5jZWRfdHJhaW5fZGF0YSlbMl1dKQogICAgdHJhaW5fcGNhXzUwMCA8LSBkYXRhLmZyYW1lKHBjYV81MDAkeFssMTo1MDBdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gYmFsYW5jZWRfdHJhaW5fZGF0YVtkaW0oYmFsYW5jZWRfdHJhaW5fZGF0YSlbMl1dKQogICAgcHJlZF9wY2FfNTAwIDwtIHByZWRpY3QocGNhXzUwMCxiYWxhbmNlZF90ZXN0X2RhdGFbLC1kaW0oYmFsYW5jZWRfdGVzdF9kYXRhKVsyXV0pCiAgICB0ZXN0X3BjYV81MDAgPC0gZGF0YS5mcmFtZShwcmVkX3BjYV81MDBbLDE6NTAwXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gYmFsYW5jZWRfdGVzdF9kYXRhW2RpbShiYWxhbmNlZF90ZXN0X2RhdGEpWzJdXSkKICAgIHNhdmUodHJhaW5fcGNhXzUwMCwgZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfcGNhX3RyYWluLlJEYXRhIikKICAgIHNhdmUodGVzdF9wY2FfNTAwLCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV9wY2FfdGVzdC5SRGF0YSIpICAKICB9IGVsc2UgewogICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV9wY2FfdHJhaW4uUkRhdGEiKQogICAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV9wY2FfdGVzdC5SRGF0YSIpICAKICB9CiAgI2NhbGN1bGF0ZSB0aGUgdHJhaW5pbmcgdGltZQogIGxkYV9wY2FfNTAwIDwtIGxkYShsYWJlbCB+IC4sIGRhdGEgPSB0cmFpbl9wY2FfNTAwLCBjdiA9IFRSVUUpCiAgdHJhaW4ubW9kZWwuZW5kID0gcHJvYy50aW1lKCkKfQpgYGAKCiogQ2FsY3VsYXRlIHRoZSBUcmFpbmluZyBhbmQgVGVzdGluZyBBY2N1cmFjeSBvZiBMREEgTW9kZWwKCmBgYHtyfQppZihydW4ucGNhX2xkYSl7CiAgdGVzdC5tb2RlbC5zdGFydCA9IHByb2MudGltZSgpCiAgcHJlZF90cmFpbl9sZGEgPC0gcHJlZGljdChsZGFfcGNhXzUwMCwgdHJhaW5fcGNhXzUwMFstZGltKHRyYWluX3BjYV81MDApWzJdXSkKICBhY2N1X3RyYWluX2xkYSA8LSBtZWFuKHByZWRfdHJhaW5fbGRhJGNsYXNzID09IHRyYWluX3BjYV81MDAkbGFiZWwpCiAgY2F0KCJUaGUgdHJhaW5pZyBhY2N1cmFjeSBvZiBtb2RlbDogTERBIiwgImlzIiwgYWNjdV90cmFpbl9sZGEqMTAwLCAiJS5cbiIpCiAgI2NhbGN1bGF0aW5nIHRoZSB0ZXN0IHRpbWUKICBpZihydW4udGVzdCl7CiAgICBwcmVkX3Rlc3RfbGRhIDwtIHByZWRpY3QobGRhX3BjYV81MDAsIHRlc3RfcGNhXzUwMCkKICB9CiAgdGVzdC5tb2RlbC5lbmQgPSBwcm9jLnRpbWUoKQogIHNhdmUocHJlZF90ZXN0X2xkYSwgZmlsZT0iLi4vb3V0cHV0L2ZpdF90cmFpbi5SRGF0YSIpCiAgYWNjdV90ZXN0X2xkYSA8LSBtZWFuKHByZWRfdGVzdF9sZGEkY2xhc3MgPT0gdGVzdF9wY2FfNTAwJGxhYmVsKQogIGNhdCgiVGhlIGFjY3VyYWN5IG9mIG1vZGVsOiBMREEiLCAiaXMiLCBhY2N1X3Rlc3RfbGRhKjEwMCwgIiUuXG4iKQogIHRwci5mcHIgPC0gV2VpZ2h0ZWRST0MoYXMubnVtZXJpYyhwcmVkX3Rlc3RfbGRhJGNsYXNzKSwgdGVzdF9wY2FfNTAwJGxhYmVsKQogIGxkYV9hdWMgPSBXZWlnaHRlZEFVQyh0cHIuZnByKQogIGNhdCgiVGhlIEFVQyBvZiBtb2RlbDogTERBIGlzIiwgbGRhX2F1YywgIi5cbiIpCn0KYGBgCgoqIFN1bW1hcml6ZSBSdW5uaW5nIFRpbWUKClByZWRpY3Rpb24gcGVyZm9ybWFuY2UgbWF0dGVycywgc28gZG9lcyB0aGUgcnVubmluZyB0aW1lcyBmb3IgY29uc3RydWN0aW5nIGZlYXR1cmVzIGFuZCBmb3IgdHJhaW5pbmcgdGhlIG1vZGVsLCBlc3BlY2lhbGx5IHdoZW4gdGhlIGNvbXB1dGF0aW9uIHJlc291cmNlIGlzIGxpbWl0ZWQuCgpgYGB7cn0KaWYocnVuLnBjYV9sZGEpewogIHRtX3RyYWluIDwtIHRyYWluLm1vZGVsLmVuZCAtIHRyYWluLm1vZGVsLnN0YXJ0CiAgdG1fdGVzdCA8LSB0ZXN0Lm1vZGVsLmVuZCAtIHRlc3QubW9kZWwuc3RhcnQKICBjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0cmFpbmluZyBmZWF0dXJlcyA9IiwgdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQogIGNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIHRlc3RpbmcgZmVhdHVyZXMgPSIsIHRtX2ZlYXR1cmVfdGVzdFsxXSwgInMgXG4iKQogIGNhdCgiVGltZSBmb3IgdHJhaW5pbmcgbW9kZWwgPSIsIHRtX3RyYWluWzFdLCAicyBcbiIpCiAgY2F0KCJUaW1lIGZvciB0ZXN0aW5nIG1vZGVsID0iLCB0bV90ZXN0WzFdLCAicyBcbiIpCn0KYGBgCgoKCiMgIEFsdGVybmF0aXZlIE1vZGVsIDQ6IFJhbmRvbSBGb3Jlc3QgKFJGKSBNb2RlbCB3aXRoIG9sZCBmZWF0dXJlczoKClRoZSBmb3VydGggYWx0ZXJuYXRpdmUgbW9kZWwgaXMgcmFuZG9tIGZvcmVzdCB1c2luZyBvbGQgZmVhdHVyZXMgZ2l2ZW4gaW4gdGhlIHN0YXJ0ZXIgY29kZS4gSGVyZSB3ZSB1c2UgdGhlIGRhdGFzZXRzIHRoYXQgYXJlIGV4dHJhY3RlZCBieSBvbGQgZmVhdHVyZSBmdW5jdGlvbnMuIFdlIHVzZWQgdHdvIG1vZGVscyB0cmFpbmVkIGJ5IGJvdGggaW1iYWxhbmNlZCBhbmQgYmFsYW5jZWQgZGF0YXNldC4gV2UgdXNlZCBST1NFIGZ1bmN0aW9uIHRvIGJhbGFuY2UgYm90aCB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhLiBGb3IgdHVuaW5nIHRoZSBtb2RlbCwgd2Ugc2V0IG10cnkgPSAzMDgsIHRyZWUgbnVtYmVyID0gNTAwLCBhbmQgbm9kZSBzaXplID0gMTAgZm9yIHRoZSBSRiBtb2RlbCB1c2luZyBiYWxhbmNlZCBkYXRhLCBhbmQgd2Ugc2V0IG10cnkgPSAzMDgsIHRyZWUgbnVtYmVyID0gMTUwMCwgYW5kIG5vZGUgc2l6ZSA9IDMwIGZvciB0aGUgUkYgbW9kZWwgdXNpbmcgaW1iYWxhbmNlZCBkYXRhLiBUaGUgZXZhbHVhdGlvbiBvZiB0aGUgbW9kZWwgaXMgc2hvd24gYXQgdGhlIGVuZCBvZiB0aGlzIHNlY3Rpb24sIGFuZCB3ZSB3aWxsIGNvbXBhcmUgdGhpcyBtb2RlbCB3aXRoIHRoZSBSRiBtb2RlbCB0cmFpbmVkIGJ5IG5ldyBmZWF0dXJlcy4KCmBgYHtyfQppZihydW4ucmYub2xkLmZlYXR1cmUpewogIG9sZF9mZWF0dXJlIDwtIGZ1bmN0aW9uKGlucHV0X2xpc3QgPSBmaWR1Y2lhbF9wdF9saXN0LCBpbmRleCl7CiAgICBvbGRfcGFpcndpc2VfZGlzdCA8LSBmdW5jdGlvbih2ZWMpewogICAgICByZXR1cm4oYXMudmVjdG9yKGRpc3QodmVjKSkpCiAgICB9CiAgICBvbGRfcGFpcndpc2VfZGlzdF9yZXN1bHQgPC1mdW5jdGlvbihtYXQpewogICAgICByZXR1cm4oYXMudmVjdG9yKGFwcGx5KG1hdCwgMiwgb2xkX3BhaXJ3aXNlX2Rpc3QpKSkKICAgIH0KICAgIG9sZF9wYWlyd2lzZV9kaXN0X2ZlYXR1cmUgPC0gdChzYXBwbHkoaW5wdXRfbGlzdFtpbmRleF0sIG9sZF9wYWlyd2lzZV9kaXN0X3Jlc3VsdCkpCiAgICBkaW0ob2xkX3BhaXJ3aXNlX2Rpc3RfZmVhdHVyZSkKICAgIG9sZF9wYWlyd2lzZV9kYXRhIDwtIGNiaW5kKG9sZF9wYWlyd2lzZV9kaXN0X2ZlYXR1cmUsIGluZm8kbGFiZWxbaW5kZXhdKQogICAgY29sbmFtZXMob2xkX3BhaXJ3aXNlX2RhdGEpIDwtIGMocGFzdGUoImZlYXR1cmUiLCAxOihuY29sKG9sZF9wYWlyd2lzZV9kYXRhKS0xKSwgc2VwID0gIiIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImxhYmVsIikKICAgIG9sZF9wYWlyd2lzZV9kYXRhIDwtIGFzLmRhdGEuZnJhbWUob2xkX3BhaXJ3aXNlX2RhdGEpCiAgICBvbGRfcGFpcndpc2VfZGF0YSRsYWJlbCA8LSBhcy5mYWN0b3Iob2xkX3BhaXJ3aXNlX2RhdGEkbGFiZWwpCiAgICByZXR1cm4oZmVhdHVyZV9kZiA9IG9sZF9wYWlyd2lzZV9kYXRhKQogIH0KCiAgb2xkX3RtX2ZlYXR1cmVfdHJhaW4gPC0gTkEKICBpZihydW4uZmVhdHVyZS50cmFpbil7CiAgICBvbGRfdG1fZmVhdHVyZV90cmFpbiA8LSBzeXN0ZW0udGltZShvbGRfZGF0X3RyYWluIDwtIG9sZF9mZWF0dXJlKGZpZHVjaWFsX3B0X2xpc3QsIHRyYWluX2lkeCkpCiAgICBzYXZlKG9sZF9kYXRfdHJhaW4sIGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3RyYWluX29sZC5SRGF0YSIpCiAgfWVsc2V7CiAgICBsb2FkKGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3RyYWluX29sZC5SRGF0YSIpCiAgfQogIG9sZF90bV9mZWF0dXJlX3Rlc3QgPC0gTkEKICBpZihydW4uZmVhdHVyZS50ZXN0KXsKICAgIG9sZF90bV9mZWF0dXJlX3Rlc3QgPC0gc3lzdGVtLnRpbWUob2xkX2RhdF90ZXN0IDwtIG9sZF9mZWF0dXJlKGZpZHVjaWFsX3B0X2xpc3QsIHRlc3RfaWR4KSkKICAgIHNhdmUob2xkX2RhdF90ZXN0LCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90ZXN0X29sZC5SRGF0YSIpCiAgfWVsc2V7CiAgICBsb2FkKGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3Rlc3Rfb2xkLlJEYXRhIikKICB9CgogICMgVHJhaW4gTW9kZWwKICBpZih0cmFpbi5yZi5vbGQuZmVhdHVyZSl7CiAgICAjIHRyYW5zZmVyIGxhYmVsIGNvbHVtbiBmcm9tIGZhY3RvciB0byBudW1lcmljCiAgICBvbGRfZGF0X3RyYWluJGxhYmVsIDwtIGFzLm51bWVyaWMob2xkX2RhdF90cmFpbiRsYWJlbCkKICAgIG9sZF9kYXRfdGVzdCRsYWJlbCA8LSBhcy5udW1lcmljKG9sZF9kYXRfdGVzdCRsYWJlbCkKICAKICAgICMgQmFsYW5jZSBkYXRhCiAgICBkYXRfdHJhaW4kbGFiZWwgPC0gYXMuZmFjdG9yKGRhdF90cmFpbiRsYWJlbCkKICAgIG9sZF9kYXRfdHJhaW5fYmFsYW5jZWRfU01PVEUgPC0gU01PVEUobGFiZWwgfiAuLCBkYXRfdHJhaW4sIHBlcmMub3ZlciA9IDEwMCwgcGVyYy51bmRlcj0yMDApCiAgICAjIEJhbGFuY2VkCiAgICBvbGRfdGltZS5yZi50cmFpbi5maW5hbC5iYWxhbmNlZCA8LSBzeXN0ZW0udGltZSgKICAgICAgb2xkX3JhbmRvbV9mb3Jlc3RfZml0X2ZpbmFsX2JhbGFuY2VkIDwtIG9sZF9yYW5kb21fZm9yZXN0X3RyYWluKG9sZF9kYXRfdHJhaW5fYmFsYW5jZWRfU01PVEUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gMzA4LCB0cmVlX251bWJlciA9IDEwMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBub2RlX3NpemUgPSAzMCkpCiAgICBzYXZlKG9sZF9yYW5kb21fZm9yZXN0X2ZpdF9maW5hbF9iYWxhbmNlZCwKICAgICAgICAgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fZmluYWxfYmFsYW5jZWRfb2xkX2ZlYXR1cmUuUkRhdGEiKQogICAgc2F2ZShvbGRfdGltZS5yZi50cmFpbi5maW5hbC5iYWxhbmNlZCwKICAgICAgICAgZmlsZSA9ICIuLi9vdXRwdXQvcmZfdHJhaW5fZmluYWxfdGltZV9iYWxhbmNlZF9vbGRfZmVhdHVyZS5SRGF0YSIpCiAgIAogIH1lbHNlewogICAgbG9hZCgiLi4vb3V0cHV0L3JmX3RyYWluX2ZpbmFsX2JhbGFuY2VkX29sZF9mZWF0dXJlLlJEYXRhIikKICAgIGxvYWQoIi4uL291dHB1dC9yZl90cmFpbl9maW5hbF90aW1lX2JhbGFuY2VkX29sZF9mZWF0dXJlLlJEYXRhIikKICB9CgoKICAjIEV2YWx1YXRlIE1vZGVsCiAgb2xkX3JmX2RhdF90ZXN0IDwtIG9sZF9kYXRfdGVzdAogIG9sZF9yZl9kYXRfdGVzdCRsYWJlbCA8LSBhcy5udW1lcmljKG9sZF9yZl9kYXRfdGVzdCRsYWJlbCkKICBpZih0ZXN0LnJmLm9sZC5mZWF0dXJlKXsKICAgIG9sZF90aW1lLnJmLnRlc3QuZmluYWwuYmFsYW5jZWQgPC0gc3lzdGVtLnRpbWUoCiAgICAgIHJmX3ByZWRpY3RlZF9iYWxhbmNlZCA8LSBhcy5udW1lcmljKGFzLnZlY3RvcihvbGRfcmFuZG9tX2ZvcmVzdF90ZXN0KG9sZF9yYW5kb21fZm9yZXN0X2ZpdF9maW5hbF9iYWxhbmNlZCwgb2xkX3JmX2RhdF90ZXN0KSkpKQogICAgb2xkX3JmX2FjY3VyYWN5X2JhbGFuY2VkIDwtIG1lYW4ocm91bmQocmZfcHJlZGljdGVkX2JhbGFuY2VkID09IG9sZF9yZl9kYXRfdGVzdCRsYWJlbCkpCiAgICBvbGRfdHByLmZwci5iYWxhbmNlZCA8LSBXZWlnaHRlZFJPQyhhcy5udW1lcmljKHJmX3ByZWRpY3RlZF9iYWxhbmNlZCksb2xkX3JmX2RhdF90ZXN0JGxhYmVsKQogICAgb2xkX3JmX0FVQ19iYWxhbmNlZCA8LSBXZWlnaHRlZEFVQyhvbGRfdHByLmZwci5iYWxhbmNlZCkKCiAgICBjYXQoIkFVQyhiYWxhbmNlZCkgZm9yIFJhbmRvbSBGb3Jlc3Qgd2l0aCBvbGQgZmVhdHVyZTogIiwgCiAgICAgICAgb2xkX3JmX0FVQ19iYWxhbmNlZCwiLlxuIikKICAgIGNhdCgiQWNjdXJhY3koYmFsYW5jZWQpIGZvciBSYW5kb20gRm9yZXN0IHdpdGggb2xkIGZlYXR1cmUiLCAKICAgICAgICBvbGRfcmZfYWNjdXJhY3lfYmFsYW5jZWQqMTAwLCIlLlxuIikKICAgIGNhdCgiVHJhaW5pbmcgdGltZSAoYmFsYW5jZWQpIGZvciBSYW5kb20gRm9yZXN0IHdpdGggb2xkIGZlYXR1cmU6ICIsIAogICAgICAgIG9sZF90aW1lLnJmLnRyYWluLmZpbmFsLmJhbGFuY2VkWzFdLCAicy5cbiIpCiAgICBjYXQoIlRlc3RpbmcgdGltZSAoYmFsYW5jZWQpIGZvciBSYW5kb20gRm9yZXN0IHdpdGggb2xkIGZlYXR1cmU6ICIsIAogICAgICAgIG9sZF90aW1lLnJmLnRlc3QuZmluYWwuYmFsYW5jZWRbMV0sICJzLlxuIikKICAgIGNhdCgiICAgIiwiXG4iKQogIH0KfQpgYGAKCiMgR2VuZXJhdGUgYSBjc3Ygb24gcHJlc2VudGF0aW9uIGRheQoKYGBge3IgZ2VuZXJhdGUgY3N2fQppZiAocnVuLnByZXNlbnRhdGlvbi5kYXkpewogIGNzdmZpbGVvdXRwdXQ8LSIuLi9vdXRwdXQvbGFiZWxfcHJlZGljdGlvbi5jc3YiCiAgQWR2YW5jZWQ8LXJmX3ByZWRpY3RlZF9iYWxhbmNlZAogIEJhc2VsaW5lPC1sYWJlbF9wcmVkX2Jhc2VsaW5lCiAgSW5kZXg8LXRlc3RfaWR4CiAgY3N2ZGF0YSA8LSBkYXRhLmZyYW1lKEluZGV4LCBCYXNlbGluZSwgQWR2YW5jZWQpCgogIHdyaXRlLmNzdihjc3ZkYXRhLGNzdmZpbGVvdXRwdXQsIHJvdy5uYW1lcz1GQUxTRSxxdW90ZSA9IEZBTFNFKQp9CmBgYAoKIyMjIFJlZmVyZW5jZQotIER1LCBTLiwgVGFvLCBZLiwgJiBNYXJ0aW5leiwgQS4gTS4gKDIwMTQpLiBDb21wb3VuZCBmYWNpYWwgZXhwcmVzc2lvbnMgb2YgZW1vdGlvbi4gUHJvY2VlZGluZ3Mgb2YgdGhlIE5hdGlvbmFsIEFjYWRlbXkgb2YgU2NpZW5jZXMsIDExMSgxNSksIEUxNDU0LUUxNDYyLgo=